線程同步與雙隊列

關於線程同步與雙隊列性能

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上測試的出來的,而本文實在家裏寫的,所以數據沒有在這裏給出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章