關於std::thread以及std::condition_variable的一些細節備忘
也算是看過不少多線程相關的資料了,但是一直對於其中的一些細節沒有太好的把握,比如std::thread線程真正開始運行的時機,比如join、detch等真正的作用。
跟著《Cplusplus Concurrency In Action_Practical Multithreading》又過了一遍相關的細節,下面記錄一下一些個人所獲得的收獲。
std::thread真正開始運行的時機
下面是我嘗試寫的一個基於條件變量和互斥量的生產者消費者模型的Demo,就從這裏開始說起
#include<iostream> #include<thread> #include<unistd.h> #include<vector> #include<mutex> #include<condition_variable> #include<cmath> std::vector<int> resource; std::mutex m; std::condition_variable cv; void thread_procedure(){ while(true){ int n; while(true){ std::unique_lock<std::mutex> ul(m); cv.wait(ul,[&]{return !resource.empty();}); if(!resource.empty()) { n = resource.back(); resource.pop_back(); break; } } int res = 0; for(int i = 0; i<n; ++i){ //sleep(2); res+=i; } std::lock_guard<std::mutex> ll(m); std::cout<<"This is from Thread "<<std::this_thread::get_id()<<", "<<"The sum of 0+1+2+...+"<<n<<" is "<<res<<std::endl; } } int main(){ std::thread tt[5]; for(int i = 0; i<5; ++i){ tt[i] = std::thread(thread_procedure); } for(int i = 0; i<5; ++i){ tt[i].join(); } while(true){ std::lock_guard<std::mutex> lg(m); resource.push_back(rand()%100); cv.notify_one(); } }
這段代碼使用了一個std::mutex和一個std::condition_variable控制相應的線程,嘗試實現一個簡單的打印功能。
但在運行時該代碼會卡在生產者處,即join代碼之後的while循環不會運行下去。。。
這裏幾乎就涉及了std::thread線程庫裏面對於線程啟動的機制以及join的真正語義了。
下面是一段GNU對於std::thread的實現代碼:
class thread { ... public: thread() noexcept = default; thread(thread&) = delete; thread(const thread&) = delete; thread(thread&& __t) noexcept { swap(__t); } template<typename _Callable, typename... _Args> explicit thread(_Callable&& __f, _Args&&... __args) { _M_start_thread(_M_make_routine(std::__bind_simple( std::forward<_Callable>(__f), std::forward<_Args>(__args)...))); } ... };
可以看到thread的構造函數傳入了一個_Callable可調用對象以及相關的參數,然後使用了std::__bind_simple進行了包裝,相當於std::bind,然後使用_M_start_thread直接使用平臺相關線程實現開啟了這個線程!
從這裏我們可以看出在每個std::thread構造完成的時候新線程就已經開啟了!
而join函數的作用就是等待join的線程執行結束,在join返回之後繼續運行後續代碼。
這樣上面的代碼會卡住也就理所應當了,在開啟新線程之後資源池就用完了,然後啟動的線程都阻塞在條件變量上面,而後續的while循環裏面的生產過程則是由於join函數在等待已經開啟的線程結束而無法運行。
整個程序就會在所有線程都處於阻塞狀態下停在那裏。
而解決這個問題的方法倒也簡單,另開一個生產者線程就行,如下代碼:
#include<iostream> #include<thread> #include<unistd.h> #include<vector> #include<mutex> #include<condition_variable> #include<cmath> std::vector<int> resource; std::mutex m; std::condition_variable cv; void thread_procedure(){ while(true){ int n; while(true){ std::unique_lock<std::mutex> ul(m); cv.wait(ul,[&]{return !resource.empty();}); if(!resource.empty()) { n = resource.back(); resource.pop_back(); break; } } int res = 0; for(int i = 0; i<n; ++i){ res+=i; } std::lock_guard<std::mutex> ll(m); std::cout<<"This is from Thread "<<std::this_thread::get_id()<<", "<<"The sum of 0+1+2+...+"<<n<<" is "<<res<<std::endl; } } void producer(){ while(true){ std::lock_guard<std::mutex> lg(m); resource.push_back(rand()%100); cv.notify_one(); } } int main(){ std::thread tt[6]; for(int i = 0; i<5; ++i){ tt[i] = std::thread(thread_procedure); } tt[5] = std::thread(producer); for(int i = 0; i<6; ++i){ tt[i].join(); } }
這種情況下,所有工作線程都在join函數調用之前就開啟了,也就不會存在上述問題了。
關於std::thread以及std::condition_variable的一些細節備忘