概覽
從C++11開始提供了線程的支持,終於可以方便的編寫跨平台的線程代碼了。除了std::thread類,還提供了許多其它便利同步的機制,本篇總結是C++11學習筆記系列的首篇總結。
std::thread
std::thread定義在<thread>
中,提供了方便的創建線程的功能。
類定義
- class thread
- {
- public:
- thread() noexcept;
- thread( thread&& other ) noexcept;
- template< class Function, class... Args >
- explicit thread( Function&& f, Args&&... args );
- thread(const thread&) = delete;
- ~thread();
- thread& operator=( thread&& other ) noexcept;
- bool joinable() const noexcept;
- std::thread::id get_id() const noexcept;
- native_handle_type native_handle();
- void join();
- void detach();
- void swap( thread& other ) noexcept;
- static unsigned int hardware_concurrency() noexcept;
- };
從定義中我們可以得知:
- std::thread不支持拷貝語義。
- std::thread支持移動語義。
各個成員函數的簡單介紹
- join() 可以用來等待線程結束,只能調用一次。
- joinable()是否與某個有效的線程關聯。
- detach() 與當前線程分離。
- swap() 與另外一個std::thread交換。
- get_id()獲取id。
- native_handle() 返回平台相關的數據,windows下是HANDLE。
- hardware_concurrency() 返回可並行運行的線程數量,只能作為一個參考。
例子
因為thread類比較簡單,我們通過幾個例子來學習。
- 支持移動語義,但不支持拷貝語義
- void some_function() {}
- void some_other_function() {}
- int main()
- {
- std::thread t1(some_function); // 構造一個thread對象t1
- std::thread t2 = std::move(t1); // 把t1 move給另外一個thread對象t2,t1不再管理之前的線程了。
- // 這句不需要std::move(),從臨時變量進行移動是自動和隱式的。調用的是operator=(std::thread&&)
- t1 = std::thread(some_other_function);
- std::thread t3;
- t3 = std::move(t2); // 把t2 move給t3
- // 把t3 move給t1,非法。因為`t1`已經有了一個相關的線程,會調用`std::terminate()`來終止程序。
- t1 = std::move(t3);
- }
- 通過調用join()成員函數來等待線程結束
- void foo()
- {
- std::this_thread::sleep_for(std::chrono::seconds(1));
- }
- int main()
- {
- std::cout << "starting first helper...\n";
- std::thread helper1(foo);
- helper1.join();
- }
- 傳遞參數給線程函數
- void f(int i, std::string const& s);
- std::thread t(f, 3 "hello");
注意:參數會以默認的方式被復制到內部存儲空間,直到使用的時候才會轉成對應的類型。
下面的例子有問題嗎?有什么問題?
- void f(int i, std::string const& s);
- void oops(int some_param)
- {
- char buffer[1024];
- sprintf(buffer, "%i", some_param);
- std::thread t(f, 3, buffer);
- t.detach();
- }
局部變量buffer
的指針會被傳遞給新線程,如果oops()
在buffer
被轉換成string
之前退出,那么會導致未定義的行為。解決之道是在構造std::thread
的時候傳遞string
變量。std::thread t(f, 3, std::string(buffer));
可以使用std::ref()
來顯示表明要傳遞引用,就像std::bind()
那樣。
- std::thread t(update_data_for_widget, w, std::ref(data));
- 使用類的成員函數作為線程參數
- class CRunner
- {
- public:
- void run0(){}
- void run1(int a) {}
- void run2(int a, int b) const {}
- int run3(int a, char b, const std::string& c) {return 0;}
- int run4(int& a, double b, float c, char d) { ++a; return 0; }
- static void run_static(int a) {}
- };
- int main()
- {
- CRunner runner;
- int a = 0;
- // 使用std::mem_fun,需要傳指針
- std::thread t0(std::mem_fun(&CRunner::run0), &runner);
- // 使用std::mem_fun_ref,可以傳引用或副本
- std::thread t1(std::mem_fun_ref(&CRunner::run1), std::ref(runner), 1);
- // std::thread t1(std::mem_fun_ref(&CRunner::run1), runner, 1);
- // 使用std::mem_fn,std::mem_fn支持多於一個參數的函數,std::mem_fun不支持。
- std::thread t2(std::mem_fn(&CRunner::run2), std::ref(runner), 1, 2);
- // 使用std::bind + std::mem_fn
- std::thread t3(std::bind(std::mem_fn(&CRunner::run3), &runner, 1, 2, "data"));
- // 使用std::mem_fn,注意std::ref的用法,如果不用std::ref行不行?
- std::thread t4(std::mem_fn(&CRunner::run4), &runner, std::ref(a), 2.2, 3.3f, 'd');
- // 使用類的靜態函數
- std::thread t5(&CRunner::run_static, 1);
- t0.join();
- t1.join();
- t2.join();
- t3.join();
- t4.join();
- t5.join();
- }
注:
- std::mem_fun需要與類指針配合,std::mem_fun_ref可以和類引用或類副本配合。
- std::mem_fun或std::mem_fun_ref只支持最多一個參數的可調用對象(函數,仿函數等),在新標准中已經廢棄不用了。
- std::mem_fn是std::mem_fun的增強版,不需要區分傳遞指針或者傳遞引用。而且可以支持傳遞多個參數。
- std::thread只需要一個可以調用的對象(函數,仿函數等),所以我們也可以通過std::bind()的返回值來作為std::thread的參數。
更多
雖然在之前的例子中的函數有返回值,但是我們卻不能獲得,想獲得返回值我們需要使用std::future,關於std::future的總結,后續會慢慢補充,敬請期待。
參考資料
- 《C++並發編程實戰》
- cppreference