互斥鎖完成
#include <iostream>
#include <deque>
#include <thread>
#include <mutex>std::deque<int> q;
std::mutex mtx;static void produce(int val) {while(val--) {std::unique_lock<std::mutex> guard(mtx);q.push_front(val);mtx.unlock();std::this_thread::sleep_for(std::chrono::seconds(1));}
}
static void consumer() {int data = INT_MAX;while(data != 0) {std::unique_lock<std::mutex> guard(mtx);if (!q.empty()) {data = q.back();q.pop_back();std::cout << data << std::endl;mtx.unlock();} else {mtx.unlock();}}
}
void test() {std::thread t1(produce,3);std::thread t2(consumer);t1.join();t2.join();
}int main() {test();return 0;
}
效果如下:
9
8
7
6
5
4
3
2
1
0
Process finished with exit code 1
produce在生產過程中,std::this_thread::sleep_for (std::chrono::seconds(1));
表示延時1s,所以生產過程很慢。
consumer存在著一個while循環,只有在接收到表示結束的數據的時候,才會停止,每次循環內部,都是先加鎖,判斷隊列不空,然后就取出一個數,最后解鎖。這樣其實做了很多無用功,并且CPU占用率很高
可以在consumer內部也加一個小延時,在一次判斷后,如果發現隊列是空的,那就懲罰一下自己,延時一下,減少CPU的占用率。
static void consumer() {int data = INT_MAX;while(data != 0) {std::unique_lock<std::mutex> guard(mtx);if (!q.empty()) {data = q.back();q.pop_back();std::cout << data << std::endl;mtx.unlock();} else {mtx.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(500));}}
}
條件變量改進模型
c++11提供了#include <condition_variable>
頭文件,std::condition_variable
可以和std::mutex
結合一起使用,其中有兩個重要的接口,notify_one()
和wait()
。
wait()
可以讓線程陷入休眠狀態,在消費者生產者模型中,如果生產者發現隊列中沒有東西,就可以讓自己休眠.notify_one()
就是喚醒處于wait中的其中一個條件變量.
那什么時刻使用notify_one()比較好呢,當然是在生產者往隊列中放數據的時候了,隊列中有數據,就可以趕緊叫醒等待中的線程起來干活了。
下面是主要修改代碼:
std::condition_variable cond;static void produce(int val) {while(val--) {std::unique_lock<std::mutex> guard(mtx);q.push_front(val);mtx.unlock();cond.notify_one(); // 提醒一個waiting的線程std::this_thread::sleep_for(std::chrono::seconds(1));}
}
static void consumer() {int data = INT_MAX;while(data != 0) {std::unique_lock<std::mutex> guard(mtx);// 如果隊列為空,就一直等直到被notify_one喚醒while(q.empty())cond.wait(guard);data = q.back();q.pop_back();mtx.unlock();std::cout << data << std::endl;}
}
此時CPU的占用率也很低,因為在消費者端,隊列為空時,將控制權交給了cpu,直到被喚醒。
需要注意的是在判斷隊列是否為空的時候,使用的是while(q.empty())
,而不是if(q.empty())
這是因為wait()
從阻塞到返回,不一定就是由于notify_one()
函數造成的,還有可能由于系統的不確定原因喚醒(可能和條件變量的實現機制有關),這個的時機和頻率都是不確定的,被稱作偽喚醒,如果在錯誤的時候被喚醒了,執行后面的語句就會錯誤,所以需要再次判斷隊列是否為空,如果還是為空,就繼續wait()
阻塞。
在管理互斥鎖的時候,使用的是std::unique_lock
而不是std::lock_guard
,在上一篇筆記C++多線程快速入門(二)共享數據同步以及數據競爭中,談到過ock_guard沒有lock和unlock接口,而unique_lock提供了。這里的話也是由于此點原因。因為在wait()
函數之前,使用互斥鎖保護了,如果wait的時候什么都沒做,豈不是一直持有互斥鎖?那生產者也會一直卡住,不能夠將數據放入隊列中了。所以,wait()
函數會先調用互斥鎖的unlock()
函數,然后再將自己睡眠,在被喚醒后,又會繼續持有鎖,保護后面的隊列操作。
另外除了notify_one()
函數,c++還提供了notify_all()
函數,可以同時喚醒所有處于wait狀態的條件變量。
參考
https://blog.csdn.net/qq_43145072/article/details/103732176
往期內容回顧
C++多線程快速入門(二)共享數據同步以及數據競爭
C++多線程快速入門(一):基本&常用操作