1. 程式人生 > >多執行緒實現生產者與消費者模式

多執行緒實現生產者與消費者模式

生產者-消費者模式的簡介:

在實際的軟體開發過程中,我們將產生資料的模組稱為生產者,處理資料的模組成為消費者。但僅有這兩者還不夠成為一個生產者-消費者模式,還需要有一個緩衝區(一段記憶體區域)作為中介,生產者產生的資料放入緩衝區,消費者從緩衝區讀取資料並處理。(注:上述所說的模組是廣義的,可以是類,函式,執行緒,程序等)

我們可以將這二者之間的關係圖表示出來:

這裡寫圖片描述

總結:我們用3-2-1的方法來簡單描述一個生產者-消費者模式,3代表有三種關係:生產者-生產者,消費者-消費者,生產者-消費者;2代表兩個角色:生產者,消費者;1代表一個交易場所:緩衝區

生產者-消費者模式之間的三種關係:

模擬實現生產者-消費者模式之前,我們需要先捋清除這之間的關係:

  • 生產者-生產者:很明顯,這兩者之間必定是一種競爭關係,也就是說一個生產者往緩衝區放資料時另一個生產者就不能去訪問這塊空間
  • 消費者-消費者:同樣,兩個消費者之間也是競爭的關係,這就好比兩個人同時看中一件商品時,他們之間就是一種競爭的關係
  • 生產者-消費者:生產者與消費者之間其實是一種同步與互斥的關係,假設只有一個生產者一個消費者時,只有生產者放入資料後消費者才能讀取,消費者拿到資料後生產者才去生產,這就是一種同步;但當生產者生產資料的時候消費者就不能從緩衝區拿資料,或者消費者讀資料的時候生產者就不能往緩衝區裡寫資料,否則很可能會導致兩者都存/取資料失敗,產生二義性問題。

生產者-單消費者的實現:

  • 方案一:使用互斥鎖+條件變數
    我們現在模擬一個場景,生產者將生產的資料放入交易場所,消費者從中讀取。這個場所我們就用單鏈表來模擬(它還可以是任意一個數據結構,只要能夠儲存資料),生產者放入資料時進行頭插,消費者消費資料時進行頭刪(這裡我採用的是頭插頭刪的方式,當然也可以用尾插、尾刪的方式實現):

這裡寫圖片描述

既然已經確定了生產場所,其次我們要考慮到如何才能讓這二者時間實現同步與互斥。多執行緒程式設計中提到了“條件變數(Condition Variable)”,在此,我們再次簡單回顧一下這幾個函式:(文章連結:http://blog.csdn.net/qq_33951180/article/details/72801651

//初始化一個條件變數
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 
int pthread_cond_init(pthread_cond_t *restrict cond,
                      const pthread_condattr_t *restrict attr);  

//銷燬一個條件變數
int pthread_cond_destroy(pthread_cond_t *cond);  

//喚醒等待的執行緒 
int pthread_cond_signal(pthread_cond_t *cond); //喚醒一個 
int pthread_cond_broadcast(pthread_cond_t *cond);  //喚醒所有

//阻塞等待  
int pthread_cond_wait(pthread_cond_t *restrict cond,
                      pthread_mutex_t *restrict mutex);
//設定超時等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                       pthread_mutex_t *restrict mutex,  
                       const struct timespec *restrict abstime);  

我們可以利用條件變數中的相關函式來模擬實現,當生產者執行緒生產資料時消費者執行緒就wait,當消費者被喚醒後,此時連結串列已經有資料(消費者等待成功),拿走結點;消費者讀取資料的過程中生產者在wait,等到消費者讀取資料完之後被喚醒,然後繼續往緩衝區寫入資料(迴圈………),等待與被喚醒的過程就可以利用上述的pthread_cond_wait和pthread_cond_signal函式實現。

  • 方案二:使用訊號量實現
    我們將方案一中的連結串列這次該為環形佇列,環形佇列使用陣列模擬實現(迴圈連結串列也可以),如圖:

這裡寫圖片描述

我們將陣列想象成上圖中的環形佇列,生產者和消費者都從0號下標的位置開始走,一開始,環形佇列中有N個空格子和0個數據,因此,在生產者沒有生產資料之前,消費者是不能進行消費的;
當生產者生產的資料佔滿整個環形buf時,假設消費者還在0號位置,生產者和消費者就會再次在0號位置相遇,此時有0個block資源和N個data資源,因此生產不能繼續放入資料;
當消費者和生產者都在執行時,一旦消費者追上了生產者,消費者就沒有資源可供使用了。

綜上:使用環形buf的情況下,生產者和消費者必須滿足:
①一開始,必須保證生產者先執行;
②生產者不能將消費者套圈(參考上述第二點);
③消費者不能追上生產者(參考第三點)

對於生產者來說:只關心空格子(block)資源
對於消費者來說:只關心資料(data)資源

因此,我們在用訊號量實現時,需要兩個訊號量來分別表示block和data資源。

  • 兩種實現方案的比較:
    互斥鎖+條件變數實現的是有鎖同步,而訊號量實現的是無鎖同步