請問這個程序為什麼會死在18,19行?

很簡單的生產者,消費者問題。代碼如下:

#include &
#include & #include &
#include &
using namespace std;
std::condition_variable pcv,ccv;
std::mutex m,m1;
const int N=10;
int buf[N];
int count=0;
void producer(){
Sleep(100);
while(true){
std::unique_lock& pulk(m);
while(count==N)
pcv.wait(pulk);
buf[count++]=1;
cout&<&<"produce data on the buff: "&<& culk(m);
while(count==0)
ccv.wait(culk);
buf[--count]=0;
cout&<&<"consume data on the buff: "&<&

程序會在

while(count==1)
ccv.notify_one()

如果把while(count==1) 和while(count==N-1)去掉。程序就都能正常工作了。

我用GDB調試了下,發現總是在上面那兩行執行,根本切換不到消費者。

各位菊苣不要見笑


自己看看嘛:

  • producer:=N 就等 pcv;=1 就喚醒 ccv
  • consumer:=0 就等 ccv;=N-1 就喚醒 pcv

如果還看不出來的話,那就怪你變數的命名就很不直觀,「producer 的條件變數」「consumer 的條件變數」繞半天可能都想不出來到底是幹什麼鬼用的。

「條件變數」顧名思義是用來表達某種條件的,換句話說,某個事件。生產者消費者這裡有兩個事件「生產了一個東西」和「消費了一個東西」:

produced // 生產了一個東西;即原來的 ccv
consumed // 消費了一個東西;即原來的 pcv

於是,題目中代碼的邏輯就是:

  • producer:如果滿了,等待「消費了一個東西」事件;只要當前有一個東西,就不斷觸發「生產了一個東西」事件
  • consumer:如果空了,等待「生產了一個東西」事件;只要滿了,就不斷觸發「消費了一個東西」事件

問題顯而易見,兩者的後半句都不忍卒讀,代碼從語義上就不對,就別來談鎖不鎖的了。

---

p.s. 放在 wait 外部的 while 可以改為使用接受兩個參數版本的 wait。

p.p.s. 一個好好的平台無關的程序要去 include 那個 windows.h 幹啥,要實現 sleep,可以直接用標準庫的 this_thread::sleep_for:

Sleep(100);
std::this_thread::sleep_for(std::chrono::milliseconds(100));

前者 Windows Only,而且需要去查 MSDN 才知道這個 100 的單位是個啥;後者支持 C++11 的編譯器就可以用,而且 100 是毫秒毫無懸念。

年輕人可不要在 MSDN 上弔死。


std::condition_variable::wait

這個上面說:

When unblocked, regardless of the reason, lock is reacquired and wait exits

是在下傻X了。=-=


不忍卒讀啊,你對照一下這個吧:Using Condition Variables (Windows)

使用Windows API版本的condition variable來實現producer/consumer模式,反正都差不多。然後你就明白為什麼要去掉那兩句while了。

年輕人沒事要多看MSDN。


先說結論:其實你的程序陷在while循環中了。

首先去掉 while 判斷是正確的姿勢,其次哪怕 if 都比 while 好,原因後面講。但即使是 if (count == 1) { ccv.notify_one(); },假設有多個消費者,也會有問題。比如一次性生產10個(沒切換線程,生產者一口氣生產10個),也只能喚醒多個消費者的一個,因為只發出了一次notify。雖然程序不會錯,但這樣會導致並發度降低。

wait會釋放鎖,notify不會,notify不會,notify不會

如果你用的是if,pulk 會自動釋放,所以程序也能正常的跑。PS:塊的最後使用 unlock 也是不正確的姿勢,因為 lock 的析構函數會 unlock 的,lock就是干 RAII 這事的,自己 unlock 是為了在塊的中部做特殊處理用的。

查了下java的文檔,你參考下,C++是一樣的概念

The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object"s monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

根據上面說的,思考一下,你應該能明白,我開始說的結論。


正確的生產者消費者應該是

首先消費者等待在

while(count==0)
ccv.wait(culk);

然後它釋放鎖,生產者獲得鎖,然後發信號,釋放鎖。

ccv.notify_one();
pulk.unlock();

然後消費者wait返回,再次獲得鎖,才能繼續後續操作。

這裡

while(count==1)
ccv.notify_one();

生產者生產一個之後,一直在發信號,釋放不了鎖,從而消費者也無法再次獲得鎖。

另一個也是同樣道理。


推薦閱讀:

應該如何熟悉GNU工具鏈?例如GCC/Makefile/GDB
C++ 編程軟體有哪些推薦?有沒有比 vc 6 更好的?
怎樣用C++實現生產者消費者的模擬?
C++向上轉型,為什麼不需要強制轉換?
有沒有一本書,專門講各種UI效果怎麼實現的,而不是講各種庫的使用辦法的?

TAG:C | Linux開發 | 多線程 |