青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

Zero Lee的專欄

[轉]多線程隊列的算法優化

原文來自于: http://www.parallellabs.com/2010/10/25/practical-concurrent-queue-algorithm/ 

多線程隊列(Concurrent Queue)的使用場合非常多,高性能服務器中的消息隊列,并行算法中的Work Stealing等都離不開它。對于一個隊列來說有兩個最主要的動作:添加(enqueue)和刪除(dequeue)節點。在一個(或多個)線程在對一個隊列進行enqueue操作的同時可能會有一個(或多個)線程對這個隊列進行dequeue操作。因為enqueue和dequeue都是對同一個隊列里的節點進行操作,為了保證線程安全,一般在實現中都會在隊列的結構體中加入一個隊列鎖(典型的如pthread_mutex_t q_lock),在進行enqueue和dequeue時都會先鎖住這個鎖以鎖住整個隊列然后再進行相關的操作。這樣的設計如果實現的好的話一般性能就會很不錯了。以鏈表實現的隊列的結構體一般是這樣的:

01
02
03
04
05
struct queue_t {
    node_t *head;
    node_t *tail;
    pthread_mutex_t q_lock;
};

但是,這其中其實有一個潛在的性能瓶頸:enqueue和dequeue操作都要鎖住整個隊列,這在線程少的時候可能沒什么問題,但是只要線程數一多,這個鎖競爭所產生的性能瓶頸就會越來越嚴重。那么我們可不可以想辦法優化一下這個算法呢?當然可以!如果我們仔細想一想enqueue和dequeue的具體操作就會發現他們的操作其實不一定是沖突的。例如:如果所有的enqueue操作都是往隊列的尾部插入新節點,而所有的dequeue操作都是從隊列的頭部刪除節點,那么enqueue和dequeue大部分時候都是相互獨立的,我們大部分時候根本不需要鎖住整個隊列,白白損失性能!那么一個很自然就能想到的算法優化方案就呼之欲出了:我們可以把那個隊列鎖拆成兩個:一個隊列頭部鎖(head lock)和一個隊列尾部鎖(tail lock)。這樣這樣的設計思路是對了,但是如果再仔細思考一下它的實現的話我們會發現其實不太容易,因為有兩個特殊情況非常的tricky(難搞):第一種就是往空隊列里插入第一個節點的時候,第二種就是從只剩最后一個節點的隊列中刪除那個“最后的果實”的時候。

為什么難搞呢?當我們向空隊列中插入第一個節點的時候,我們需要同時修改隊列的head和tail指針,使他們同時指向這個新插入的節點,換句話說,我們此時即需要拿到head lock又需要拿到tail lock。而另一種情況是對只剩一個節點的隊列進行dequeue的時候,我們也是需要同時修改head和tail指針使他們指向NULL,亦即我們需要同時獲得head和tail lock。有經驗的同學會立刻發現我們進入危險區了!是什么危險呢?死鎖!多線程編程中最臭名昭著的一種bug就是死鎖了。例如,如果線程A在鎖住了資源1后還想要獲取資源2,而線程B在鎖住了資源2后還想要獲取資源1,這時兩個線程誰都不能獲得自己想要的那個資源,兩個線程就死鎖了。所以我們要小心奕奕的設計這個算法以避免死鎖,例如保證enqueue和dequeue對head lock和tail lock的請求順序(lock ordering)是一致的等等。但是這樣設計出來的算法很容易就會包含多次的加鎖/解鎖操作,這些都會造成不必要的開銷,尤其是在線程數很多的情況下反而可能導致性能的下降。我的親身經歷就是在32線程時這個思路設計出來的算法性能反而下降了10%左右,原因就是加鎖/解鎖的開銷增加了。

好在有聰明人早在96年就想到了一個更妙的算法。這個算法也是用了head和tail兩個鎖,但是它有一個關鍵的地方是它在隊列初始化的時候head和tail指針不為空,而是指向一個空節點。在enqueue的時候只要向隊列尾部添加新節點就好了。而dequeue的情況稍微復雜點,它要返回的不是頭節點,而是head->next,即頭節點的下一個節點。先來看偽代碼:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
typedef struct node_t {
    TYPE value;
    node_t *next
} NODE;
 
typedef struct queue_t {
    NODE *head;
    NODE *tail;
    LOCK q_h_lock;
    LOCK q_t_lock;
} Q;
 
initialize(Q *q) {
   node = new_node()   // Allocate a free node
   node->next = NULL   // Make it the only node in the linked list
   q->head = q->tail = node   // Both head and tail point to it
   q->q_h_lock = q->q_t_lock = FREE   // Locks are initially free
}
 
enqueue(Q *q, TYPE value) {
   node = new_node()       // Allocate a new node from the free list
   node->value = value     // Copy enqueued value into node
   node->next = NULL       // Set next pointer of node to NULL
   lock(&q->q_t_lock)      // Acquire t_lock in order to access Tail
      q->tail->next = node // Link node at the end of the queue
      q->tail = node       // Swing Tail to node
   unlock(&q->q_t_lock)    // Release t_lock
 
dequeue(Q *q, TYPE *pvalue) {
   lock(&q->q_h_lock)   // Acquire h_lock in order to access Head
      node = q->head    // Read Head
      new_head = node->next       // Read next pointer
      if new_head == NULL         // Is queue empty?
         unlock(&q->q_h_lock)     // Release h_lock before return
         return FALSE             // Queue was empty
      endif
      *pvalue = new_head->value   // Queue not empty, read value
      q->head = new_head  // Swing Head to next node
   unlock(&q->q_h_lock)   // Release h_lock
   free(node)             // Free node
   return TRUE            // Queue was not empty, dequeue succeeded
}

發現玄機了么?是的,這個算法中隊列總會包含至少一個節點。dequeue每次返回的不是頭節點,而是頭節點的下一個節點中的數據:如果head->next不為空的話就把這個節點的數據取出來作為返回值,同時再把head指針指向這個節點,此時舊的頭節點就可以被free掉了。這個在隊列初始化時插入空節點的技巧使得enqueue和dequeue徹底相互獨立了。但是,還有一個小地方在實現的時候需要注意:對第一個空節點的next指針的讀寫。想象一下,當一個線程對一個空隊列進行第一次enqueue操作時剛剛運行完第25行的代碼(對該空節點的next指針進行寫操作);而此時另一個線程對這個隊列進行第一次dequeue操作時恰好運行到第33行(對該空節點的next指針進行讀操作),它們其實還是有沖突!不過,好在一般來講next指針是32位數據,而現代的CPU已經能保證多線程程序中內存對齊了的32位數據讀寫操作的原子性,而一般來講編譯器會自動幫你對齊32位數據,所以這個不是問題。唯一需要注意的是我們要確保enqueue線程是先讓要添加的新節點包含好數據再把新節點插入鏈表(也就是不能先插入空節點,再往節點中填入數據),那么dequeue線程就不會拿到空的節點。其實我們也可以把q_t_lock理解成生產者的鎖,q_h_lock理解成消費者的鎖,這樣生產者(們)和消費者(們)的操作就相互獨立了,只有在多個生產者對同一隊列進行添加操作時,以及多個消費者對同一隊列進行刪除操作時才需要加鎖以使訪問互斥。

通過使用這個算法,我成功的把一個32線程程序的性能提升了11%!可見多線程中的鎖競爭對性能影響之大!此算法出自一篇著名的論文:M. Michael and M. Scott. Simple, Fast, and Practical Non-Blocking and Blocking Concurren Queue Algorithms. 如果還想做更多優化的話可以參考這篇論文實現相應的Non Blocking版本的算法,性能還能有更多提升。當然了,這個算法早已被集成到java.util.concurrent里了(即LinkedBlockingQueue),其他的并行庫例如Intel的TBB多半也有類似的算法,如果大家能用上現成的庫的話就不要再重復造輪子了。為什么別造并行算法的輪子呢?因為高性能的并行算法實在太難正確地實現了,尤其是Non Blocking,Lock Free之類的“火箭工程”。有多難呢?Doug Lea提到java.util.concurrent中一個Non Blocking的算法的實現大概需要1年的時間,總共約500行代碼。所以,對最廣大的程序員來說,別去寫Non Blocking, Lock Free的代碼,只管用就行了,我看見網上很多的Non Blocking阿,無鎖編程的算法實現啊什么的都非常地害怕,誰敢去用他們貼出來的這些代碼啊?我之所以推薦這個two lock的算法是因為它的實現相對Non Blocking之類的來說容易多了,非常具備實用價值。雖然這篇論文出現的很早,但是我在看了幾個開源軟件中多線程隊列的實現之后發現他們很多還是用的本文最開始提到的那種一個鎖的算法。如果你想要實現更高性能的多線程隊列的話,試試這個算法吧!

Update: 多線程隊列算法有很多種,大家應根據不同的應用場合選取最優算法(例如是CPU密集型還是IO密集型)。本文所列的算法應用在這樣一個多線程程序中:每個線程都擁有一個隊列,每個隊列可能被本線程進行dequeue操作,也可以被其他線程進行dequeue(即work stealing),線程數不超過CPU核心數,是一個典型的CPU/MEM密集型客戶端單寫者多讀者場景。

posted on 2012-06-05 14:26 Zero Lee 閱讀(1267) 評論(0)  編輯 收藏 引用

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美一区视频| 亚洲一区免费观看| 久久字幕精品一区| 亚洲一级二级在线| 国产在线一区二区三区四区| 欧美美女日韩| 欧美v日韩v国产v| 欧美激情中文不卡| 欧美国产在线视频| 亚洲自拍啪啪| 久久男女视频| 亚洲欧洲一区二区三区在线观看| 久久久亚洲综合| 亚洲国产欧美在线| 亚洲精品久久视频| 亚洲视频精选| 一区二区三区视频观看| 在线视频日韩精品| 亚洲在线播放| 欧美日韩系列| 亚洲国产精品悠悠久久琪琪| 亚洲一区图片| 免费在线成人av| 国产精品天天看| 一本到12不卡视频在线dvd| 欧美在线国产精品| 亚洲激情在线视频| 另类图片综合电影| 伊人精品视频| 午夜电影亚洲| 国产欧美一级| 亚洲免费观看高清完整版在线观看| 一区二区三区视频观看| 亚洲二区在线观看| 久久免费视频网| 亚洲电影免费观看高清完整版| 欧美一区深夜视频| 在线亚洲+欧美+日本专区| 亚洲国产精品成人久久综合一区| 亚洲理伦在线| 麻豆精品一区二区av白丝在线| 在线中文字幕一区| 国产精品毛片va一区二区三区| 99视频精品全部免费在线| 欧美激情亚洲另类| 欧美高清在线一区| 99在线观看免费视频精品观看| 玖玖精品视频| 在线日韩精品视频| 美日韩精品免费观看视频| 小黄鸭精品aⅴ导航网站入口| 国产日韩欧美一区| 欧美午夜在线视频| 久久国产乱子精品免费女 | 91久久精品日日躁夜夜躁国产| 久久精品论坛| 久久日韩粉嫩一区二区三区| 亚洲破处大片| 一本色道久久综合亚洲91| 国产美女精品视频免费观看| 欧美黄色一区| 国产精品自拍一区| 欧美激情按摩在线| 国产一区三区三区| 最新国产成人av网站网址麻豆| 久久久久久亚洲精品不卡4k岛国| 亚洲人成在线播放网站岛国| 欧美一区二区三区啪啪| 这里只有精品丝袜| 欧美国产在线电影| 欧美国产日本在线| 亚洲国产精品成人一区二区| 亚洲一区二区三区涩| 亚洲视频在线一区| 欧美日韩精品是欧美日韩精品| 蜜臀a∨国产成人精品| 在线精品高清中文字幕| 久久久久成人精品| 欧美激情91| 久久蜜桃精品| 欧美国产日韩xxxxx| 91久久精品国产91久久性色| 欧美日韩1区| 一区二区免费在线播放| 亚洲一区中文| 悠悠资源网亚洲青| 欧美激情一区二区三区在线| 亚洲精品中文在线| 欧美一区二区三区喷汁尤物| 激情久久影院| 欧美精品久久一区二区| 亚洲综合电影| 亚洲精品视频一区二区三区| 亚洲在线观看| 亚洲人成网站777色婷婷| 国产精品白丝av嫩草影院| 久久不射电影网| 日韩写真视频在线观看| 麻豆成人在线观看| 亚洲嫩草精品久久| 亚洲精品在线观| 精品成人在线| 国产日产精品一区二区三区四区的观看方式 | 中日韩美女免费视频网址在线观看 | 亚洲国产精品热久久| 欧美一级欧美一级在线播放| 国产综合自拍| 午夜久久资源| 欧美大片一区二区三区| 欧美日韩国产首页| 国产婷婷一区二区| 亚洲视频狠狠| 国产精品永久免费在线| 国产亚洲人成网站在线观看| 久久九九免费| 久久九九99视频| 亚洲欧美激情视频| 亚洲一区成人| 欧美一区二区三区免费视频| 亚洲网站视频| 午夜精品美女久久久久av福利| 中文国产一区| 性久久久久久久久久久久| 亚洲视屏一区| 欧美呦呦网站| 欧美国产视频在线| 欧美日韩免费高清一区色橹橹| 欧美视频手机在线| 国产精品v欧美精品v日本精品动漫| 欧美精品国产精品日韩精品| 狂野欧美一区| 亚洲一区尤物| 欧美日韩国产区| 欧美xxx成人| 国产综合久久| 欧美一区二区高清在线观看| 亚洲精品美女在线观看播放| 裸体一区二区| 国产一区二区三区奇米久涩| 亚洲视频欧美视频| 久久精品二区| 亚洲一二区在线| 国产精品日本欧美一区二区三区| 亚洲日韩欧美视频| 亚洲精品美女| 欧美日韩岛国| 亚洲欧美日韩一区| 亚洲一区精品视频| 欧美三级午夜理伦三级中视频| av成人免费观看| 亚洲乱码国产乱码精品精98午夜| 欧美精品一区二区三区蜜桃 | 日韩视频精品在线| 国产精品久久久久久久第一福利| 欧美日韩一区二区视频在线观看 | 欧美性淫爽ww久久久久无| 一区二区三区精品久久久| 欧美风情在线| 国产精品一卡二| 久久动漫亚洲| 美日韩丰满少妇在线观看| 精品不卡一区二区三区| 91久久国产综合久久蜜月精品| 欧美电影在线观看| 亚洲网友自拍| 久热国产精品| 欧美一区在线直播| 中文网丁香综合网| 国产精品一页| 亚洲欧洲一区二区在线播放 | 亚洲三级观看| 国产欧美日韩一区二区三区在线观看 | 亚洲精品乱码久久久久久按摩观| 激情综合电影网| 麻豆精品网站| 蜜桃av噜噜一区| 在线看片欧美| 美玉足脚交一区二区三区图片| 久久这里只有精品视频首页| 国产美女一区二区| 欧美伊人久久久久久久久影院| 性亚洲最疯狂xxxx高清| 午夜在线a亚洲v天堂网2018| 欧美日韩色一区| 亚洲天堂第二页| 午夜伦欧美伦电影理论片| 国产日本欧美视频| 久久精品中文字幕一区二区三区| 欧美中文字幕在线观看| 国产欧美日韩亚洲| 欧美成年人视频网站| 亚洲四色影视在线观看| 久久不射2019中文字幕| 欧美日韩一区二区三区四区在线观看| 蜜臀av性久久久久蜜臀aⅴ| 亚洲国产欧美一区二区三区同亚洲| 久久久久久一区| 亚洲激情婷婷| 在线视频欧美日韩| 欧美视频在线一区二区三区|