关于线程同步与双队列性能

1问题背景

2第一种方式,共享队列

3第二种方式,双队列

4遇到的问题

关于线程同步与双队列性能

2009712星期日

1问题背景

这是在20083月学习多线程编程时遇到的一个问题。当时我写了一个代码片段,其中两个线程共享一个队列,一个线程往队列中写数据,而另一个线程从队列中读取数据。这是典型的生产者和消费者模型。但在这里并不适合使用semaphore来做。

由于当时的我对多线程编程不太熟练,在线程中大量使用了printf输出调试信息,printf是典型的IO操作会引起线程的切换,所以打出来的信息也显示了线程切换十分频繁,几乎每一个数据入队列后,就立即被另一个线程抢占,并出队列,队列的长度一直为01,处理效率非常低。

在本周四,我有一个与架构师讨论问题的机会,我便找架构师请教了这个问题。当时我给架构师描述了我的问题,也许是我自己基础不好,架构师并不能很好的理解我的意思,但架构师根据我的描述给了我一些意见:

1、  如果是两个线程交互,那么除了加锁之外,还可以使用原子操作(atom)来代替。

2、  如果两个线程共享同一个队列,无可避免的就是其中一个线程的在操作这个队列时,另外一个只能等着,效率很低。建议采用双队列技术,可以提高并行效率。这一点在后面的实验中得到证实,使用双队列技术后效率提高了一个数量级!

3、  操作系统一般10ms-20ms调度一次,所以应该尽量避免加锁,因为加锁会陷入内核。增加开销。

4、  尽量避免使用printf这种同步IO,建议分配一个大的缓冲,用于存储输出信息,在写满后一次性输出取出,这样就避免频繁的阻塞线程,导致调度频繁。

根据架构师的建议,我在公司里完成了这个程序,使用了两种方式:共享队列和双队列。

2第一种方式,共享队列

两个线程共享一个队列,一个往里写,一个往外读,我将写入数据的那个线程叫做writer,读出数据的那个线程叫做reader。这样,writer线程在工作时,reader线程只能等待,reader线程在工作时写线程只能等待。有人会说,可以多来几个reader这样可以使用RWLOCK,但根据应用场景,这里就只有一个消费者。这是一种最简单的方式,也是最基本的方式,效率也很低。在这里就不再讨论这个方式的实现了。

3第二种方式,双队列

两个线程各自一个队列,writer线程往队列1写数据,reader线程从队列2读数据,而在队列2为空时,我将队列1中的数据拼接或交换到队列2中,这样只有这个短暂的交换或拼接动作需要加锁。

那么这里还有个问题,当队列1中有多少数据后拼接到队列2的效率是最高的呢?架构师给了一个建议,先以16个、32个、64个这样的步进进行测试,直到找到性能最好的队列拼接长度。而我最后没有使用架构师给出的这个建议,而是模拟内核与用于空间之间的网络读写接口,这样就把这个任务给了系统调度,大概策略如下:

1、  如果reader线程发现队列2和队列1中都没有数据,则陷入内核睡眠状态,等待event唤醒。

2、  Writer线程当往队列1中写数据时,发现队列1和队列2都没有数据,则认为reader线程肯定会等待在event上,则向reader线程出发event。由于操作系统规则,event类似于condition variable,不会马上被唤醒。所以writer有机会往队列1中继续写入一些数据,其实在多核CPU情况下,writer一直可以往队列1中写数据,除非队列1reader线程锁住。

3、  Reader线程被event唤醒之后,会干以下事情

a)         如果队列2是空的,那么就锁住队列1和队列2,然后将队列1中的数据一次性拼接到队列2中。

b)        Reader线程开始读取队列2中的数据,这里需要注意的是如果是多核CPU,这时writer线程可能正在往队列1中写数据,这与reader线程处理是完全并行的,互不相干,所以双队列在多核CPU上效率很高。

c)         如果reader线程将队列2读空之后,发现writer线程已经又向队列1中写入了一些数据,那么从步骤a)开始重复执行。

d)        如果reader线程将队列2读空之后,发现队列1也是空的,那么说明没有数据了,这时reader线程又回到event上睡眠,等待writer线程的event通知。

根据测试双队列的性能后,发现比共享队列的性能高一个数量级(10倍),这些测试数据在公司里的Intel P4 HT 3.0G 1.5GMEM上测试的出来的,而本文实在家里写的,所以数据没有在这里给出。

 

posted on 2011-05-10 20:08  30斤大番薯  阅读(2314)  评论(0编辑  收藏  举报