C++11多執行緒
發現好多人點贊,這個是我轉發的,我把原作者連結發下面許多人沒看到。支援原創,去原作者這裡看看,
原文:Balimango:C++11多執行緒
1、std::thread
在C++11之前,C++語言層面是不支援多執行緒的,想利用C++實現併發程式,藉助作業系統的API實現跨平臺的併發程式存在著諸多不便,當C++11在語言層面支援多執行緒後,編寫跨平臺的多執行緒程式碼就方便了許多。
C++11提供的std::thread在開發多執行緒方面帶來了便捷。
#include
#include
void threadfunc()
{
std::cout << “thread func” << std::endl;
}
int main()
{
std::thread t1(threadfunc);
t1。join(); //等待threadfunc執行結束
return 0;
}
首先定義執行緒物件t1,執行緒函式threadfunc執行線上程物件t1中,當執行緒建立成功並執行執行緒函式後,一定要保證執行緒函式執行結束才能退出,這裡呼叫了join()函式阻塞執行緒,直到threadfunc()執行結束,回收對應建立執行緒的資源。如果不阻塞執行緒,就不能保證執行緒物件t1在threadfunc()執行期間有效,下面不呼叫join()阻塞執行緒。
#include
#include
void threadfunc()
{
std::cout << “thread func” << std::endl;
}
int main()
{
std::thread t1(threadfunc);
//t1。join(); //等待threadfunc執行結束
return 0;
}
在執行時引起了程式崩潰。
除了呼叫join()阻塞執行緒,保證執行緒物件線上程函式執行期間的有效性,還可以透過執行緒分離的手段實現,呼叫detach()函式使得執行緒物件與執行緒函式分離,這樣,線上程函式執行期間,執行緒物件與執行緒函式就沒有聯絡了,此時的執行緒是作為後臺執行緒去執行,detach()後就無法再和執行緒發生聯絡,也不能透過join()來等待執行緒執行完畢,執行緒何時執行完無法控制,它的資源會被init程序回收,所以,通常不採用detach()方法。
#include
#include
void threadfunc()
{
std::cout << “ detach thread func” << std::endl;
}
int main()
{
std::thread t1(threadfunc);
t1。detach(); //執行緒分離
return 0;
}
這裡呼叫detach()實現執行緒分離,但是執行後,主執行緒退出的時候threadfunc()還沒有輸出“detach thread func”,threadfunc()什麼時候執行結束也無法確定,為了看到所建立的執行緒執行結果,在主執行緒等待一下再退出。
#include
#include
#include
void threadfunc()
{
std::cout << “detach thread func” << std::endl;
}
int main()
{
std::thread t1(threadfunc);
t1。detach();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
此時執行結果:
detach thread func
透過std::thread建立的執行緒是不可以複製的,但是可以移動。
#include
#include
#include
void threadfunc()
{
std::cout << “move thread func” << std::endl;
}
int main()
{
std::thread t1(threadfunc);
std::thread t2(std::move(t1));
t2。join();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
輸出結果:
move thread func
移動後t1就不代表任何執行緒了,t2物件代表著執行緒threadfunc()。另外,還可以透過std::bind來建立執行緒函式。
#include
#include
#include
#include
class A {
public:
void threadfunc()
{
std::cout << “bind thread func” << std::endl;
}
};
int main()
{
A a;
std::thread t1(std::bind(&A::threadfunc,&a));
t1。join();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
建立一個類A,然後再main函式中將類A中的成員函式繫結到執行緒物件t1上,執行結果:
bind thread func
每個執行緒都有自己的執行緒標識,也就是執行緒ID,當執行緒建立成功後,可以透過get_id()來獲取執行緒的ID。
#include
#include
#include
#include
class A {
public:
void threadfunc()
{
std::cout << “bind thread func” << std::endl;
}
};
int main()
{
A a;
std::thread t1(std::bind(&A::threadfunc,&a));
std::cout << “main thread ID is : ” << std::this_thread::get_id() << std::endl;
std::cout << “t1 thread ID is : ” << t1。get_id() << std::endl;
t1。join();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
std::this_thread::get_id()獲取的是當前執行緒的ID,t1。get_id()獲取的是所建立的t1物件中執行的執行緒ID,對應的ID分別為:
main thread ID is : 11932
t1 thread ID is : 12076
bind thread func
雖然get_id()可以獲取執行緒的ID,但是其返回型別是thread::id,透過std::cout可以輸出執行緒ID,但是這樣使用似乎不太方面,要是能轉換為整形就好了。其實可以將得到的執行緒ID寫入到ostreamstring流中,轉換成string型別,再轉換成整形。
#include
#include
#include
#include
#include
class A {
public:
void threadfunc()
{
std::cout << “bind thread func” << std::endl;
}
};
int main()
{
A a;
std::thread t1(std::bind(&A::threadfunc, &a));
std::ostringstream os1;
os1 << t1。get_id() << std::endl;
std::string strID = os1。str(); //轉換成string型別
int threadID = atoi(strID。c_str()); //轉換成int型別
std::cout << “t1 thread ID is : ” << threadID << std::endl;
t1。join();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
輸出結果:
t1 thread ID is : 6956
bind thread func
2、std::mutex
進入多執行緒程式設計的世界,除了要牢牢掌握std::thread使用方法,還要掌握互斥量(鎖)的使用,這是一種執行緒同步機制,在C++11中提供了4中互斥量。
std::mutex; //非遞迴的互斥量
std::timed_mutex; //帶超時的非遞迴互斥量
std::recursive_mutex; //遞迴互斥量
std::recursive_timed_mutex; //帶超時的遞迴互斥量
從各種互斥量的名字可以看出其具有的特性,在實際開發中,常用就是std::mutex,它就像是一把鎖,我們需要做的就是對它進行加鎖與解鎖。
#include
#include
#include
#include
std::mutex g_mutex;
void func()
{
std::cout << “entry func test thread ID is : ” << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::microseconds(1000));
std::cout << “leave func test thread ID is : ” << std::this_thread::get_id() << std::endl;
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
std::thread t4(func);
std::thread t5(func);
t1。join();
t2。join();
t3。join();
t4。join();
t5。join();
return 0;
}
建立了5個執行緒,然後分別呼叫func()函式,得到結果:
entry func test thread ID is : entry func test thread ID is : 19180
entry func test thread ID is : 3596
13632
entry func test thread ID is : 9520
entry func test thread ID is : 4460
leave func test thread ID is : 13632
leave func test thread ID is : 19180
leave func test thread ID is : leave func test thread ID is : 9520
3596
leave func test thread ID is : 4460
可以看出,並沒有按順序去執行執行緒函式,後面建立的執行緒並沒有等待前面的執行緒執行完畢,導致結果混亂,下面用std::mutex進行控制:
#include
#include
#include
#include
std::mutex g_mutex;
void func()
{
g_mutex。lock();
std::cout << “entry func test thread ID is : ” << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::microseconds(1000));
std::cout << “leave func test thread ID is : ” << std::this_thread::get_id() << std::endl;
g_mutex。unlock();
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
std::thread t4(func);
std::thread t5(func);
t1。join();
t2。join();
t3。join();
t4。join();
t5。join();
return 0;
}
只要執行緒進入func()函式就進行加鎖處理,當執行緒執行完畢後進行解鎖,保證每個執行緒都能按順序執行,輸出結果:
entry func test thread ID is : 8852
leave func test thread ID is : 8852
entry func test thread ID is : 15464
leave func test thread ID is : 15464
entry func test thread ID is : 17600
leave func test thread ID is : 17600
entry func test thread ID is : 16084
leave func test thread ID is : 16084
entry func test thread ID is : 4156
leave func test thread ID is : 4156
雖然透過lock()與unlock()可以解決執行緒之間的資源競爭問題,但是這裡也存在不足。
func()
{
//加鎖
執行邏輯處理; //如果該過程丟擲異常導致程式退出了,就沒法unlock
//解鎖
}
int main()
{
……
}
func()中再執行邏輯處理中程式因為某些原因退出了,此時就無法unlock()了,這樣其他執行緒也就無法獲取std::mutex,造成死鎖現象,其實在加鎖之前可以透過trylock()嘗試一下能不能加鎖。實際開發中,通常也不會這樣寫程式碼,而是採用lock_guard來控制std::mutex。
template
class lock_guard {
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx)
{
_MyMutex。lock(); //建構函式加鎖
}
lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx)
{
}
~lock_guard() noexcept
{
_MyMutex。unlock(); //解構函式解鎖
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
lock_guard是類模板,在其建構函式中自動給std::mutex加鎖,在退出作用域的時候自動解鎖,這樣就可以保證std::mutex的正確操作,這也是RAII(獲取資源便初始化)技術的體現。
#include
#include
#include
#include
std::mutex g_mutex;
void func()
{
std::lock_guard
std::cout << “entry func test thread ID is : ” << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::microseconds(1000));
std::cout << “leave func test thread ID is : ” << std::this_thread::get_id() << std::endl;
//退出作用域後,lock_guard物件析構就自動解鎖
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
std::thread t4(func);
std::thread t5(func);
t1。join();
t2。join();
t3。join();
t4。join();
t5。join();
return 0;
}
執行結果:
entry func test thread ID is : 19164
leave func test thread ID is : 19164
entry func test thread ID is : 15124
leave func test thread ID is : 15124
entry func test thread ID is : 2816
leave func test thread ID is : 2816
entry func test thread ID is : 17584
leave func test thread ID is : 17584
entry func test thread ID is : 15792
leave func test thread ID is : 15792
3、std::condition_variable
條件變數是C++11提供的另外一種執行緒同步機制,透過判斷條件是否滿足,決定是否阻塞執行緒,當執行緒執行條件滿足的時候就會喚醒阻塞的執行緒,常與std::mutex配合使用,C++11提供了兩種條件變數。
std::condition_variable,配合std::unique_lock
std::condition_variable_any,可以和任意帶有lock()、unlock()語義的std::mutex搭配使用,比較靈活,但是其效率不及std::condition_variable;
std::unique_lock:C++11提供的 std::unique_lock 是通用互斥包裝器,允許延遲鎖定、鎖定的有時限嘗試、遞迴鎖定、所有權轉移和與條件變數一同使用。std::unique_lock比std::lock_guard使用更加靈活,功能更加強大。使用std::unique_lock需要付出更多的時間、效能成本。
下面利用std::mutex與std::condition_variable實現生產者與消費者模式。
#include
#include
#include
#include
#include
#include
class CTask {
public:
CTask(int taskID)
{
this->taskId = taskID;
}
void dotask()
{
std::cout << “consumer a task Id is ” << taskId << std::endl;
}
private:
int taskId;
};
std::list
std::mutex g_mutex;
std::condition_variable g_conv;
//生產者執行緒
void ProdecerFunc()
{
int n_taskId = 0;
std::shared_ptr
while (true)
{
ptask = std::make_shared
{
std::lock_guard
g_task。push_back(ptask);
std::cout << “produce a task Id is ” << n_taskId << std::endl;
}
//喚醒執行緒
g_conv。notify_one();
n_taskId++;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
//消費者執行緒
void ConsumerFunc()
{
std::shared_ptr
while (true)
{
std::unique_lock
while (g_task。empty()) //即使被喚醒還要迴圈判斷一次,防止虛假喚醒
{
g_conv。wait(lock);
}
ptask = g_task。front(); //取出任務
g_task。pop_front();
if (ptask == nullptr)
{
continue;
}
ptask->dotask(); //執行任務
}
}
int main()
{
std::thread t1(ConsumerFunc);
std::thread t2(ConsumerFunc);
std::thread t3(ConsumerFunc);
std::thread t4(ProdecerFunc);
t1。join();
t2。join();
t3。join();
t4。join();
return 0;
}
建立3個消費者執行緒,一個生產者執行緒,當存放任務的std::list為空時,消費者執行緒阻塞,當生產者執行緒生產一個任務放入std::list中時候,此時滿足條件,條件變數就可以喚醒阻塞的執行緒去執行任務。
produce a task Id is 0
consumer a task Id is 0
produce a task Id is 1
consumer a task Id is 1
produce a task Id is 2
consumer a task Id is 2
produce a task Id is 3
consumer a task Id is 3
produce a task Id is 4
consumer a task Id is 4
produce a task Id is 5
consumer a task Id is 5
produce a task Id is 6
consumer a task Id is 6
produce a task Id is 7
consumer a task Id is 7
……
條件變數的使用過程可以歸納如下:
擁有條件變數的線消費者程獲取互斥鎖;
消費者執行緒迴圈檢查條件是否滿足,不滿足則阻塞等待,此時釋放互斥鎖;
當生產者執行緒產生任務後,呼叫notify_one()或者notify_all()喚醒阻塞的消費者執行緒;
當消費者執行緒被喚醒後再次獲得互斥鎖去執行任務;
4、thread_local
C++11中提供了thread_local,thread_local定義的變數在每個執行緒都儲存一份副本,而且互不干擾,線上程退出的時候自動銷燬。
#include
#include
#include
thread_local int g_k = 0;
void func1()
{
while (true)
{
++g_k;
}
}
void func2()
{
while (true)
{
std::cout << “func2 thread ID is : ” << std::this_thread::get_id() << std::endl;
std::cout << “func2 g_k = ” << g_k << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
int main()
{
std::thread t1(func1);
std::thread t2(func2);
t1。join();
t2。join();
return 0;
}
在func1()對g_k迴圈加1操作,在func2()每個1000毫秒輸出一次g_k的值:
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
……
可以看出func2()中的g_k始終保持不變。
轉發文章。原文:Balimango:C++11多執行緒
上一篇:這是一個關於“屁”的深度研究!
下一篇:或許IG真的沒有冠軍命