您當前的位置:首頁 > 體育

C++11多執行緒

作者:由 讀無用之書 發表于 體育時間:2021-03-03

發現好多人點贊,這個是我轉發的,我把原作者連結發下面許多人沒看到。支援原創,去原作者這裡看看,

原文: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;

}

在執行時引起了程式崩潰。

C++11多執行緒

除了呼叫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 //std::bind

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 lock(g_mutex); //加鎖

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使用,透過wait()函式阻塞執行緒;

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> g_task;

std::mutex g_mutex;

std::condition_variable g_conv;

//生產者執行緒

void ProdecerFunc()

{

int n_taskId = 0;

std::shared_ptr ptask = nullptr;

while (true)

{

ptask = std::make_shared(n_taskId); //建立任務

{

std::lock_guard lock(g_mutex);

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 ptask = nullptr;

while (true)

{

std::unique_lock lock(g_mutex);

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多執行緒

標簽: std  Thread  func  執行緒  id