自c++11版本後,標準庫也提供了對線程的支持。雖然大多場合還是使用其他的三方線程庫,如:boost::thread, QThread等,但是學習下還是有必要的。
1. std::thread簡介
std::thread類即創建子線程的類,定義於頭文件thread中。
std::thread類僅不到十個公開成員函數,同時無法進行拷貝,只能使用移動構造和賦值轉移所有權。
2. CPU支持的線程併發數
#include <thread>
[static]
unsigned int std::thread::hardware_concurrency();
// 返回硬件支持的線程數目,失敗返回0
#include <iostream>
#include <thread>
int main()
{
std::cout << "CPU是" << std::thread::hardware_concurrency() << "線程" << std::endl;
return 0;
}
3. 創建子線程
#include <thread>
[public]
template<typename _Callable, typename... _Args>
std::thread::thread(_Callable&& __f, _Args&&... __args);
// 構造函數之一, 傳一個可以調用的函數或者對象作爲任務, 有子線程來處理運行這個任務。
// 未調用此構造函數的線程是無法執行的,稱爲空任務線程。
// 需要明確的是構造完成後,子線程即開始執行,而不是等join(結合)或者detach(分離)調用才執行
bool std::thread::joinable() const;
// 未被結合或者分離的非空任務線程會返回true, 否則返回false。
std::thread::id std::thread::get_id() const;
// 未被結合或者分離的非空任務線程會返回非0的整數作爲子線程的標識符
#include <iostream>
#include <thread>
void func(int a)
{
std::cout << "This function is callable: " << a << std::endl;
}
class callable_obj
{
public:
// 對象可以有類似函數的調用
void operator ()(int a)
{
std::cout << "This object is callable: " << a << std::endl;
}
};
int main()
{
// 可調用的函數初始化線程
std::thread t1(func, 1);
// // t1.get_id(): 很大的數 t1.joinable(): true
std::cout << "t1 thread_id: " << t1.get_id() << " joinable: " << t1.joinable() << std::endl;
// 可調用的對象初始化線程
std::thread t2;
// t2.get_id(): 0 t2.joinable(): false
std::cout << "t2 thread_id: " << t2.get_id() << " joinable: " << t2.joinable() << std::endl;
callable_obj obj;
// 移動賦值, 也可以用t1的直接構造
t2 = std::move(std::thread(obj, 2));
// t2.get_id(): 很大的數 t2.joinable(): true
std::cout << "t2 thread_id: " << t2.get_id() << " joinable: " << t2.joinable() << std::endl;
return 0;
}
t1 thread_id: 140549800912640 joinable: This function is callable: 11
t2 thread_id: thread::id of a non-executing thread joinable: 0
t2 thread_id: 140549792519936 joinable: 1
terminate called without an active exception
This object is callable: 2
需要主要的我們並沒有顯示執行t1和t2線程,但是線程還是執行了,但是會發生異常。
4. 子線程等待方式
#include <thread>
[public]
void std::thread::join();
// 僅當joinable爲true才能調用本函數, 否則拋出異常system_error。
// 本函數是使主線程進入阻塞狀態,等待子線程執行完成後,主線程在繼續執行,joinable函數返回false。
// 當子線程執行結束後,資源由主線程回收,主線程繼續執行後續指令。
void std::thread::detach();
// 僅當joinable爲true才能調用本函數, 否則拋出異常system_error。
// 本函數調用後主線程不會去等待子線程是否執行完成,主線程繼續執行後續指令,joinable函數返回false。
// 可能會發生主線程已經執行結束,子線程還未運行結束的情況。
// 當線程結束時,分離的線程由系統回收。
#include <iostream>
#include <thread>
void func(int a)
{
std::cout << "This function is callable: " << a << std::endl;
}
class callable_obj
{
public:
void operator ()(int a)
{
std::cout << "This object is callable: " << a << std::endl;
}
};
int main()
{
std::thread t1(func, 1);
// 分離,後面的代碼繼續處理
t1.detach();
callable_obj obj;
std::thread t2(std::thread(obj, 2));
// 線程進入阻塞, t2線程執行完成纔會執行return 0
t2.join();
return 0;
}
This function is callable: 1
This object is callable: 2
本實例並沒有什麼不對, 都會輸出,因爲主線程處理後續代碼的時間較長, t1大概率會在主線程執行完執行結束。因此,不會發生什麼特殊的情況,但是很多時候分離需要考慮到這個問題。
5. 傳參問題
傳參問題還需要多注意下,會出現一些問題。在參數是值的情況正常不會發生,但是如果參數有引用就會發生錯誤,需要使用std::ref來告訴其傳遞的是引用 。
#include <iostream>
#include <thread>
#include <functional>
void func(int a, int& b)
{
std::cout << "func: a = " << a << ", b = " << b << std::endl;
b = 7;
}
int main()
{
int a = 2, b = 2;
// std::thread t(func, a, b) 錯誤
std::thread t(func, a, std::ref(b));
t.join();
std::cout << "main: a = " << a << ", b = " << b << std::endl;
return 0;
}
func: a = 2, b = 2
main: a = 2, b = 7
6. 對線程類的薄封裝(RAll)
#include <iostream>
#include <thread>
#include <functional>
void func(int a, int& b)
{
std::cout << "func: a = " << a << ", b = " << b << std::endl;
b = 7;
}
class thread_graud
{
std::thread _t;
public:
thread_graud(std::thread& t) : _t(std::move(t)) {}
~thread_graud()
{
if(_t.joinable()) _t.join();
}
thread_graud(const thread_graud&) = delete;
thread_graud& operator =(const thread_graud&) = delete;
};
int main()
{
int a = 2, b = 2;
std::thread t(func, a, std::ref(b));
thread_graud g(t);
std::cout << "main: a = " << a << ", b = " << b << std::endl;
return 0;
}
使用中間類thread_graud來隱式調用join方法。
7. 擴展
c++11的線程是可以使用平臺的特性擴展的,沒想到吧。
#include <thread>
std::native_handle_type std::native_handle();
// 本函數會返回對於平臺下底層句柄, 其條件也是joinable爲true, 本人的系統是linux,此函數就會返回pthread_t
#include <iostream>
#include <thread>
#include <pthread.h>
void func(int a)
{
std::cout << "linux 系統調用成功 a = " << a << std::endl;
}
int main()
{
int a = 2;
std::thread t(func, a);
// 使用linux平臺下的pthread_join來調用線程
pthread_join(t.native_handle(), nullptr);
return 0;
}
linux 系統調用成功 a = 2
terminate called without an active exception
雖然可以這樣來用, 但是由於使用linux下的線程函數啓動了線程,而joinable的值並沒有發生改變,線程對象析構時還是會調用std::terminate(),導致了異常的發生。