国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

做網(wǎng)站宣傳多少錢如何快速網(wǎng)絡(luò)推廣

做網(wǎng)站宣傳多少錢,如何快速網(wǎng)絡(luò)推廣,做公司網(wǎng)站需要會(huì)什么,uniapp做網(wǎng)站多線程(C) 文章目錄 多線程(C)前言一、std::thread類1.線程的創(chuàng)建1.1構(gòu)造函數(shù)1.2代碼演示 2.公共成員函數(shù)2.1 get_id()2.2 join()2.3 detach()2.4 joinable()2.5 operator 3.靜態(tài)函數(shù)4.類的成員函數(shù)作為子線程的任務(wù)函數(shù) 二、call…

多線程(C++)


文章目錄

  • 多線程(C++)
  • 前言
  • 一、std::thread類
    • 1.線程的創(chuàng)建
      • 1.1構(gòu)造函數(shù)
      • 1.2代碼演示
    • 2.公共成員函數(shù)
      • 2.1 get_id()
      • 2.2 join()
      • 2.3 detach()
      • 2.4 joinable()
      • 2.5 operator=
    • 3.靜態(tài)函數(shù)
    • 4.類的成員函數(shù)作為子線程的任務(wù)函數(shù)
  • 二、call_once函數(shù)
    • 1.原理和介紹
    • 2.應(yīng)用-懶漢模式
  • 三、線程同步之互斥鎖(互斥量)
    • 1.std::mutex類
      • 1.1 成員函數(shù)
      • 1.2 線程同步
    • 2.std::lock_guard類
    • 3.std::recursive_mutex類
    • 4.std::timed_mutex類
  • 四、線程同步之條件變量
    • 1.生產(chǎn)者-消費(fèi)者模型
    • 2.條件變量
      • 2.1 成員函數(shù)
  • 總結(jié)


前言

C++11之前,C++語言沒有對(duì)并發(fā)編程提供語言級(jí)別的支持,這使得我們?cè)诰帉懣梢浦驳牟l(fā)程序時(shí),存在諸多的不便?,F(xiàn)在C++11中增加了線程以及線程相關(guān)的類,很方便地支持了并發(fā)編程,使得編寫的多線程程序的可移植性得到了很大的提高。


一、std::thread類

1.線程的創(chuàng)建

1.1構(gòu)造函數(shù)

//1. 默認(rèn)構(gòu)造函數(shù),構(gòu)造一個(gè)線程對(duì)象,在這個(gè)線程中不執(zhí)行任何處理動(dòng)作
thread() nonexcept;//2. 移動(dòng)構(gòu)造函數(shù),將other的線程所有權(quán)轉(zhuǎn)移給新的thread對(duì)象,之后的other不再表示執(zhí)行線程
thread(thread&& other) nonexcept;//3. 創(chuàng)建線程對(duì)象,并在該線程中執(zhí)行函數(shù)f的業(yè)務(wù)邏輯,args是要傳遞給函數(shù)f的參數(shù)(使用最多的,最有用的)
//此處使用右值引用的好處:可以延長臨時(shí)變量的壽命,等到使用完成后再進(jìn)行釋放
template<class Function,class... Args>
explicit thread(Function&& f,Args&&... args);
/*備注:
任務(wù)函數(shù)f的類型可以是如下:
1.普通函數(shù)、類成員函數(shù)、匿名函數(shù)、仿函數(shù)(這些都是可調(diào)用對(duì)象類型)
2.可以是可調(diào)用對(duì)象包裝器類型,也可以是使用綁定器綁定之后得到的類型(仿函數(shù))
*///4.使用delet顯示刪除拷貝構(gòu)造,不允許線程對(duì)象之間的拷貝
thread(const thread&)=delete;

1.2代碼演示

#include <iostream>
#include <thread>void func()
{std::cout << "children worker:" <<"xiaodu ,id:"<< std::this_thread::get_id() << std::endl;
}void func1(std::string name,int id)
{std::cout << "children worker:" << name << ",id:" << std::this_thread::get_id() << std::endl;
}int main() 
{std::cout << "主線程id:" << std::this_thread::get_id() << std::endl;//1. 創(chuàng)建空的線程對(duì)象std::thread t1;//2. 創(chuàng)建一個(gè)可用的子線程std::thread t2(func);std::thread t3(func1, "xiaoliu", 20);std::thread t4([=](int id) {std::cout << "arg id :" << id << ",id:" << std::this_thread::get_id() << std::endl;}, 18);std::thread&& t5 = std::move(t4);return 0;
}

報(bào)錯(cuò)信息
報(bào)錯(cuò)原因分析:程序啟動(dòng)之后,執(zhí)行main()函數(shù),進(jìn)程里就有一個(gè)主線程(父線程),然后接下來打印主線程id,再接下來創(chuàng)建線程t1、t2、t3、t4、t5,但是子線程創(chuàng)建后就會(huì)變成就緒態(tài),和主線程一起搶cpu時(shí)間片,誰搶到cpu時(shí)間片誰就執(zhí)行對(duì)應(yīng)的任務(wù)函數(shù),主線程大概率搶到cpu時(shí)間片,主線程執(zhí)行完就退出了,隨之退出的還有虛擬地址空間,因此,子線程就算搶到cpu時(shí)間片但是對(duì)應(yīng)的虛擬地址空間已經(jīng)沒了,所以報(bào)錯(cuò)。
報(bào)錯(cuò)的解決思路:讓主線程等待子線程執(zhí)行完任務(wù)函數(shù)再退出。

2.公共成員函數(shù)

2.1 get_id()

應(yīng)用程序啟動(dòng)后默認(rèn)只有一個(gè)線程,這個(gè)線程一般稱為主線程或父線程,通過線程類創(chuàng)建出來的線程一般稱為子線程,每個(gè)線程創(chuàng)建出來都對(duì)應(yīng)一個(gè)線程ID,這個(gè)ID是唯一的,可以通過這個(gè)ID來區(qū)分和識(shí)別各個(gè)已經(jīng)存在的線程實(shí)例,這個(gè)函數(shù)就是get_id(),函數(shù)原型如下:

// std::thread::id--長整型數(shù)
std::thread::id get_id() const noexcept;

示例程序:

#include <iostream>
#include <thread>void func(int num,std::string str)
{for (int i = 0; i < 10; i++){std::cout << "子線程:i=" << i << " num : " << num << ",str: " << str << std::endl;}
}void func1()
{for (int i = 0; i < 10; i++){std::cout << "子線程:i=" << i << std::endl;}
}int main() 
{// 獲得線程ID的兩種方法:// 方法1--在子線程或者主線程函數(shù)中調(diào)用std::this_thread::get_id()函數(shù),會(huì)得到當(dāng)前線程IDstd::cout << "主線程id:" << std::this_thread::get_id() << std::endl;std::thread t(func,520,"i love you");std::thread t1(func1);// 方法2--調(diào)用thread類的成員函數(shù)get_id()std::cout << "線程t的線程ID:" << t.get_id()<<std::endl;std::cout << "線程t1的線程ID:" << t1.get_id() << std::endl;return 0;
}

2.2 join()

join()字面意思是連接一個(gè)線程,意味著主動(dòng)地等待線程的終止(線程阻塞)。在某個(gè)線程A中通過子線程對(duì)象B調(diào)用join()函數(shù),調(diào)用這個(gè)函數(shù)的線程A被阻塞,但是子線程對(duì)象B中的任務(wù)函數(shù)會(huì)繼續(xù)執(zhí)行,當(dāng)任務(wù)執(zhí)行完畢之后join()會(huì)清理當(dāng)前子線程B中的相關(guān)資源然后返回,同時(shí),調(diào)用該函數(shù)的線程A解除阻塞繼續(xù)向下執(zhí)行。

// 如果要阻塞主線程的執(zhí)行,只需要在主線程中通過子線程對(duì)象調(diào)用這個(gè)方法即可,
// 當(dāng)調(diào)用這個(gè)方法的子線程對(duì)象中的任務(wù)函數(shù)執(zhí)行完畢之后,主線程的阻塞也就隨之解除了
void std::thread::join();

示例程序1:

#include <iostream>
#include <thread>void func()
{std::cout << "children worker:" <<"xiaodu ,id:"<< std::this_thread::get_id() << std::endl;
}void func1(std::string name,int id)
{std::cout << "children worker:" << name << ",id:" << std::this_thread::get_id() << std::endl;
}int main() 
{std::cout << "主線程id:" << std::this_thread::get_id() << std::endl;//1. 創(chuàng)建空的線程對(duì)象std::thread t1;//2. 創(chuàng)建一個(gè)可用的子線程std::thread t2(func);std::thread t3(func1, "xiaoliu", 20);std::thread t4([=](int id) {std::cout << "arg id :" << id << ",id:" << std::this_thread::get_id() << std::endl;}, 18);std::thread&& t5 = std::move(t4);t2.join();t3.join();t5.join();		// t4線程的所有權(quán)轉(zhuǎn)交給t5,t4對(duì)象失效return 0;
}

當(dāng)程序運(yùn)行到thread::join()時(shí)存在兩種情況:

  • 如果任務(wù)函數(shù)func()還沒執(zhí)行完畢,主線程阻塞,知道任務(wù)執(zhí)行完畢,主線程解除阻塞,繼續(xù)向下運(yùn)行
  • 如果任務(wù)函數(shù)func()已經(jīng)執(zhí)行完畢,主線程不會(huì)阻塞,繼續(xù)向下執(zhí)行

上述的bug通過join()函數(shù)成功解決。

示例程序2:
需求:程序中一共有三個(gè)線程,其中兩個(gè)子線程負(fù)責(zé)分段下載同一個(gè)文件,下載完畢之后,由主線程對(duì)這個(gè)文件進(jìn)行下一步處理

#include <iostream>
#include <thread>
#include <chrono>
using namespace std;void download1()
{// 模擬下載, 總共耗時(shí)500ms,阻塞線程500msthis_thread::sleep_for(chrono::milliseconds(500));cout << "子線程1: " << this_thread::get_id() << ", 下載1....完成" << endl;
}void download2()
{// 模擬下載, 總共耗時(shí)300ms,阻塞線程300msthis_thread::sleep_for(chrono::milliseconds(300));cout << "子線程2: " << this_thread::get_id() << ", 下載2....完成" << endl;
}void doSomething()
{cout << "完成...." << endl;
}int main()
{thread t1(download1);thread t2(download2);// 阻塞主線程,等待所有子線程任務(wù)執(zhí)行完畢再繼續(xù)向下執(zhí)行t1.join();t2.join();doSomething();
}

最核心的處理是在主線程調(diào)用doSomething();之前在第35、36行通過子線程對(duì)象調(diào)用了join()方法,這樣就能夠保證兩個(gè)子線程的任務(wù)都執(zhí)行完畢了,也就是文件內(nèi)容已經(jīng)全部下載完成,主線程再對(duì)文件進(jìn)行后續(xù)處理,如果子線程的文件沒有下載完畢,主線程就去處理文件,很顯然從邏輯上講是有問題的。

2.3 detach()

detach()函數(shù)的作用是進(jìn)行線程分離,分離主線程和創(chuàng)建出的子線程。在線程分離之后,主線程退出也會(huì)一并銷毀創(chuàng)建出的所有子線程,在主線程退出之前,它可以脫離主線程繼續(xù)獨(dú)立的運(yùn)行,任務(wù)執(zhí)行完畢之后,這個(gè)子線程會(huì)自動(dòng)釋放自己占用的系統(tǒng)資源。(唯一好處)(比如孩子(子線程)翅膀硬了,和家里(父線程)斷絕關(guān)系(detach),自己外出闖蕩(脫離主線程執(zhí)行任務(wù)函數(shù)),死了家里也不會(huì)給買棺材(執(zhí)行完畢后,自己釋放系統(tǒng)資源),如果家里被誅九族(主線程退出)還是會(huì)受到牽連(子線程還是會(huì)退出))。
示例程序:

#include <iostream>
#include <thread>void func()
{std::cout << "children worker:" << "xiaodu ,id:" << std::this_thread::get_id() << std::endl;
}void func1(std::string name, int id)
{std::cout << "children worker:" << name << ",id:" << std::this_thread::get_id() << std::endl;
}int main()
{std::cout << "主線程id:" << std::this_thread::get_id() << std::endl;//1. 創(chuàng)建空的線程對(duì)象std::thread t1;//2. 創(chuàng)建一個(gè)可用的子線程std::thread t2(func);std::thread t3(func1, "xiaoliu", 20);std::thread t4([=](int id){std::cout << "arg id :" << id << ",id:" << std::this_thread::get_id() << std::endl;}, 18);std::thread&& t5 = std::move(t4);t2.join();t3.join();// 子線程t5在主線程中調(diào)用detach()函數(shù),子線程和主線程分離,//從此再也不能在主線程中子線程調(diào)用成員函數(shù)進(jìn)行任何有效操作,否則會(huì)報(bào)錯(cuò)t5.detach();// 不能得到真正的線程IDt5.get_id();//  失效,報(bào)錯(cuò)//t5.join();		return 0;
}

注意事項(xiàng):線程分離函數(shù)detach()不會(huì)阻塞線程,子線程和主線程分離之后,在主線程中就不能再對(duì)這個(gè)子線程做任何控制了,比如:通過join()阻塞主線程等待子線程中的任務(wù)執(zhí)行完畢,或者調(diào)用get_id()獲取子線程的線程ID。有利就有弊,魚和熊掌不可兼得,建議使用join(),一般不用detach()。

2.4 joinable()

joinable()函數(shù)用于判斷主線程和子線程是否處理關(guān)聯(lián)(連接)狀態(tài),一般情況下,二者之間的關(guān)系處于關(guān)聯(lián)狀態(tài),該函數(shù)返回一個(gè)布爾類型:

  • 返回值為true:主線程和子線程之間有關(guān)聯(lián)(連接)關(guān)系
  • 返回值為false:主線程和子線程之間沒有關(guān)聯(lián)(連接)關(guān)系
bool joinable() const noexcept;

示例程序:

#include <iostream>
#include <thread>
#include <chrono>
using namespace std;void foo()
{std::this_thread::sleep_for(chrono::seconds(1));
}int main()
{// 創(chuàng)建一個(gè)空的線程對(duì)象,如果子線程未關(guān)聯(lián)任務(wù)函數(shù),則二者也是未關(guān)聯(lián)thread t;cout << "before starting,joinable:" << t.joinable() << endl;	//false// 匿名對(duì)象作為臨時(shí)變量,使用右值引用拷貝構(gòu)造函數(shù)t = thread(foo);cout << "after starting,joinable:" << t.joinable() << endl;		//true// 線程t調(diào)用jion函數(shù),阻塞主線程,執(zhí)行任務(wù)函數(shù)完成后,退出子線程t.join();cout << "after starting,joinable:" << t.joinable() << endl;		//false// 創(chuàng)建線程t1 thread t1(foo);cout << "after starting,joinable:" << t1.joinable() << endl;	//true// 線程分離t1.detach();cout << "after starting,joinable:" << t1.joinable() << endl;		//falsereturn 0;
}

2.5 operator=

線程中的資源是不能被復(fù)制的,因此通過=操作符進(jìn)行賦值操作最終并不會(huì)得到兩個(gè)完全的對(duì)象。

// move(1)
thread& operator=(thread&& other) noexcept;
// copy[deleted](2)
thread& operator=(thread& other) noexcept;

通過以上=操作符的重載聲明可以得知:

  • 如果other是一個(gè)右值,會(huì)將資源所有權(quán)進(jìn)行轉(zhuǎn)移
  • 如果other不是一個(gè)右值,禁止拷貝,該函數(shù)顯示被刪除(delete),不可用

3.靜態(tài)函數(shù)

thread線程類還提供了一個(gè)靜態(tài)方法,用于獲取當(dāng)前計(jì)算機(jī)的CPU核心數(shù),根據(jù)這個(gè)結(jié)果在程序中創(chuàng)建出數(shù)量相等的線程,每個(gè)線程獨(dú)自占有一個(gè)CPU核心,這些線程就不用分時(shí)復(fù)用CPU時(shí)間片,此時(shí)程序的并發(fā)效率是最高的(并行)。

static unsigned hardware_concurrency() noexcept;

示例代碼:

#include <iostream>
#include <thread>
using namespace std;int main()
{unsigned int num = thread::hardware_concurrency();cout << "hardware_concurrency = " << num << endl;return 0;
}

4.類的成員函數(shù)作為子線程的任務(wù)函數(shù)

待學(xué)TODO

二、call_once函數(shù)

1.原理和介紹

在某些特定情況下,某些函數(shù)只能在多線程環(huán)境下調(diào)用一次,比如:要初始化某個(gè)對(duì)象,而這個(gè)對(duì)象只能被初始化一次,就可以使用std::call_once()來保證函數(shù)在多線程環(huán)境下只能被調(diào)用一次。使用call_once()的時(shí)候,需要一個(gè)once_flag作為call_once()的傳入?yún)?shù),該函數(shù)的原型如下:

// 定義位于<mutex>
#include <mutex>
void call_once(std::once_flag& flag,Callable&& f,Args&&... args)
  • flag:once_flag類型的對(duì)象,要保證這個(gè)對(duì)象能夠被多個(gè)線程同時(shí)訪問到
  • f:回調(diào)函數(shù),可以傳遞一個(gè)有名函數(shù)地址,也可以指定一個(gè)匿名函數(shù)
  • args:作為實(shí)參傳遞給回調(diào)函數(shù)
    多線程操作過程中,std::call_once()內(nèi)部的回調(diào)函數(shù)只會(huì)被執(zhí)行一次,示例代碼如下:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 全局變量
once_flag flag;void do_once()
{cout << "do once function" << endl;
}void do_something()
{static int num = 1;// call_once注意事項(xiàng):flag要能被多個(gè)線程同時(shí)訪問;傳入實(shí)參參數(shù)的形式類似thread函數(shù);無返回值,void// 使用場景:在多線程中,只需要初始化一次對(duì)象(new 一個(gè)對(duì)象)call_once(flag, do_once);cout << "do something function: " << num++ << endl;
}int main()
{thread t1(do_something);thread t2(do_something);thread t3(do_something);t1.join();t2.join();t3.join();return 0;
}

call_once

2.應(yīng)用-懶漢模式

單例模式分類:

  • 懶漢式
    系統(tǒng)運(yùn)行中,實(shí)例并不存在,只有當(dāng)需要使用該實(shí)例時(shí),才會(huì)去創(chuàng)建并使用實(shí)例。這種方式要考慮線程安全。
  • 餓漢式
    系統(tǒng)一運(yùn)行,就初始化創(chuàng)建實(shí)例,當(dāng)需要時(shí),直接調(diào)用即可。這種方式本身就線程安全,沒有多線程的線程安全問題。

編寫一個(gè)單例模式(分為懶漢式和餓漢式)的類–懶漢模式。
示例程序:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 單例模式--懶漢模式
// 全局變量,保證被多個(gè)線程訪問
once_flag g_flag;// 單例類
class Base
{
public:// 實(shí)現(xiàn)單例模式首先將拷貝構(gòu)造和賦值操作顯示刪除Base(const Base& obj) = delete;Base& operator=(const Base& obj) = delete;// 靜態(tài)成員函數(shù)static Base* getInstance() {// 調(diào)用call_once函數(shù)保證多個(gè)線程訪問時(shí)只有一個(gè)對(duì)象創(chuàng)建call_once(g_flag, [&]() {ptr = new Base;cout << "單例創(chuàng)建" << endl;});return ptr;}void release(){delete ptr;ptr = nullptr;}string getName(){return name;}void setName(string Name){this->name = Name;}private:// 構(gòu)造函數(shù)設(shè)為private,不能被外界直接訪問Base() { }~Base() { }static Base* ptr;string name;
};
// 懶漢模式是運(yùn)行時(shí)才會(huì)創(chuàng)建對(duì)象,所以ptr先設(shè)置為nullptr
Base* Base::ptr = nullptr;void func(string name)
{Base::getInstance()->setName(name);std::cout<<"name:"<< Base::getInstance()->getName()<<endl;
}int main()
{thread t1(func,"路飛");thread t2(func,"埃斯");thread t3(func,"薩博");t1.join();t2.join();t3.join();Base::getInstance()->release();return 0;
}

懶漢模式
此處,很大概率會(huì)出現(xiàn)打印信息不對(duì),因?yàn)槿齻€(gè)線程同時(shí)對(duì)一塊內(nèi)存進(jìn)行了讀寫操作,所以為了讓多線程線性訪問,需要加鎖。
加鎖后的代碼程序:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 單例模式--懶漢模式
// 全局變量,保證被多個(gè)線程訪問
once_flag g_flag;// 單例類
class Base
{
public:// 實(shí)現(xiàn)單例模式首先將拷貝構(gòu)造和賦值操作顯示刪除Base(const Base& obj) = delete;Base& operator=(const Base& obj) = delete;// 靜態(tài)成員函數(shù)static Base* getInstance() {// 調(diào)用call_once函數(shù)保證多個(gè)線程訪問時(shí)只有一個(gè)對(duì)象創(chuàng)建call_once(g_flag, [&]() {ptr = new Base;cout << "單例創(chuàng)建" << endl;});return ptr;}void release(){delete ptr;ptr = nullptr;}string getName(){return name;}void setName(string Name){this->name = Name;}private:// 構(gòu)造函數(shù)設(shè)為private,不能被外界直接訪問Base() { }~Base() { }static Base* ptr;string name;
};
// 懶漢模式是運(yùn)行時(shí)才會(huì)創(chuàng)建對(duì)象,所以ptr先設(shè)置為nullptr
Base* Base::ptr = nullptr;mutex mx;
void func(string name)
{// 多個(gè)線程的共享資源是Base::getInstance()mx.lock();Base::getInstance()->setName(name);std::cout<<"name:"<< Base::getInstance()->getName()<<endl;mx.unlock();
}int main()
{thread t1(func,"路飛");thread t2(func,"埃斯");thread t3(func,"薩博");t1.join();t2.join();t3.join();Base::getInstance()->release();return 0;
}

三、線程同步之互斥鎖(互斥量)

進(jìn)行多線程編程,如果多個(gè)線程需要對(duì)同一塊內(nèi)存進(jìn)行操作,比如:同時(shí)讀、同時(shí)寫、同時(shí)讀寫對(duì)于后兩種情況來說,如果不做任何的人為干涉就會(huì)出現(xiàn)各種各樣的錯(cuò)誤數(shù)據(jù)。這是因?yàn)榫€程在運(yùn)行的時(shí)候需要先得到CPU時(shí)間片,時(shí)間片用完之后需要放棄已獲得的CPU資源,就這樣線程頻繁地在就緒態(tài)和運(yùn)行態(tài)之間切換,更復(fù)雜一點(diǎn)還可以在就緒態(tài)、運(yùn)行態(tài)、掛起態(tài)之間切換,這樣就會(huì)導(dǎo)致線程的執(zhí)行順序并不是有序的,而是隨機(jī)的混亂的。
多線程訪問
注意概念:線程同步是指讓多線程進(jìn)行線性有序的進(jìn)行,而不是同時(shí)對(duì)一塊內(nèi)存操作。
解決多線程數(shù)據(jù)混亂的方案就是進(jìn)行線程同步,最常用的就是互斥鎖,在C++11中一共提供了四種互斥鎖:

  • std::mutex:獨(dú)占的互斥鎖,不能遞歸使用 (最常用)
  • std::timed_mutex:帶超時(shí)的獨(dú)占互斥鎖,不能遞歸使用
  • std::recursive_mutex:遞歸互斥鎖,不帶超時(shí)功能
  • std::recursive_timed_mutex:帶超時(shí)的遞歸互斥鎖
    注意事項(xiàng):
  • 內(nèi)存可能涉及到多個(gè)線程的同時(shí)訪問(寫、讀寫),此時(shí)需要通過互斥鎖對(duì)內(nèi)存進(jìn)行保護(hù)
  • 使用互斥鎖鎖住的是和共享數(shù)據(jù)相關(guān)的一個(gè)代碼塊(臨界區(qū))
  • 在程序中一個(gè)共享數(shù)據(jù)對(duì)應(yīng)多個(gè)代碼塊,在鎖住這些代碼塊的時(shí)候要用同一把鎖,并且程序運(yùn)行期間不能銷毀這把互斥鎖

1.std::mutex類

1.1 成員函數(shù)

lock()函數(shù)用于給臨界區(qū)加鎖,并且只能有一個(gè)線程獲得鎖的所有權(quán),它有阻塞其他線程的作用,函數(shù)原型如下:

// 
void lock();

獨(dú)占互斥鎖對(duì)象有兩種狀態(tài):鎖定和未鎖定。如果互斥鎖是打開的,調(diào)用lock()函數(shù)的線程會(huì)得到互斥鎖的所有權(quán),并將其上鎖,其它線程再調(diào)用該函數(shù)的時(shí)候由于得不到互斥鎖的所有權(quán),就會(huì)被lock()函數(shù)阻塞。當(dāng)擁有互斥鎖所有權(quán)的線程將互斥鎖解鎖,此時(shí)被lock()阻塞的線程解除阻塞,搶到互斥鎖所有權(quán)的線程加鎖并繼續(xù)運(yùn)行,沒搶到互斥鎖所有權(quán)的線程繼續(xù)阻塞。

除了使用lock()還可以使用try_lock()獲取互斥鎖的所有權(quán)并對(duì)互斥鎖加鎖,函數(shù)原型如下:

bool try_lock();

二者的區(qū)別在于try_lock()不會(huì)阻塞線程,lock()會(huì)阻塞線程:

  • 如果互斥鎖是未鎖定狀態(tài),得到了互斥鎖所有權(quán)并加鎖成功,函數(shù)返回true
  • 如果互斥鎖是鎖定狀態(tài),無法得到互斥鎖所有權(quán)加鎖失敗,函數(shù)返回false

**當(dāng)互斥鎖被鎖定之后可以通過unlock()進(jìn)行解鎖,但是需要注意的是只有擁有互斥鎖所有權(quán)的線程也就是對(duì)互斥鎖上鎖的線程才能將其解鎖,其它線程是沒有權(quán)限做這件事情的。**該函數(shù)的函數(shù)原型如下:

void unlock();

通過介紹以上三個(gè)函數(shù),使用互斥鎖進(jìn)行線程同步的大致思路差不多就能搞清楚了,主要分為以下幾步:

  • 找到多個(gè)線程操作的共享資源(全局變量、堆內(nèi)存、類成員變量等),也可以稱之為臨界資源
  • 找到和共享資源有關(guān)的上下文代碼,也就是臨界區(qū)(下圖中的黃色代碼部分)
  • 在臨界區(qū)的上邊調(diào)用互斥鎖類的lock()方法
  • 在臨界區(qū)的下邊調(diào)用互斥鎖的unlock()方法
    線程同步
    線程同步的目的是讓多線程按照順序依次執(zhí)行臨界區(qū)代碼,這樣做線程對(duì)共享資源的訪問就從并行訪問變?yōu)榱司€性訪問,訪問效率降低了,但是保證了數(shù)據(jù)的正確性。

注意:
當(dāng)線程對(duì)互斥鎖對(duì)象加鎖,并且執(zhí)行完臨界區(qū)代碼之后,一定要使用這個(gè)線程對(duì)互斥鎖解鎖,否則最終會(huì)造成線程的死鎖。死鎖之后當(dāng)前應(yīng)用程序中的所有線程都會(huì)被阻塞,并且阻塞無法解除,應(yīng)用程序也無法繼續(xù)運(yùn)行。

1.2 線程同步

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 互斥鎖
class Base
{
public:// count表示打印的個(gè)數(shù)void increment(int count) {for (int i = 0; i < count; i++){mx.lock();++num;cout << "++current number: " << num << endl;mx.unlock();this_thread::sleep_for(chrono::milliseconds(100));}}// count表示打印的個(gè)數(shù)void decrement(int count){for (int i = 0; i < count; i++){mx.lock();--num;cout << "--current number: " << num << endl;mx.unlock();this_thread::yield();}}private:// 此處為共享資源int num=999;mutex mx;
};int main()
{Base b;thread t1(&Base::increment,&b,10);thread t2(&Base::decrement, &b, 10);t1.join();t2.join();return 0;
}

結(jié)果

2.std::lock_guard類

lock_guard是C++11新增的一個(gè)模板類,使用這個(gè)類,可以簡化互斥鎖lock()和unlock()的寫法,同時(shí)也更安全。這個(gè)模板類的定義和常用的構(gòu)造函數(shù)原型如下:

// 類的定義,定義于頭文件 <mutex>
template< class Mutex >
class lock_guard;// 常用構(gòu)造函數(shù)
explicit lock_guard( mutex_type& m );

lock_guard在使用上面提供的這個(gè)構(gòu)造函數(shù)構(gòu)造對(duì)象時(shí),會(huì)自動(dòng)鎖定互斥量,而在退出作用域后進(jìn)行析構(gòu)時(shí)就會(huì)自動(dòng)解鎖,從而保證了互斥量的正確操作,避免忘記unlock()操作而導(dǎo)致線程死鎖。lock_guard使用了RAII技術(shù),就是在類構(gòu)造函數(shù)中分配資源,在析構(gòu)函數(shù)中釋放資源,保證資源出了作用域就釋放。
使用lock_guard對(duì)上面的例子進(jìn)行修改,代碼如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 互斥鎖
class Base
{
public:// count表示打印的個(gè)數(shù)void increment(int count) {for (int i = 0; i < count; i++){{lock_guard<mutex> lock(mx);++num;cout << "++current number: " << num << endl;}this_thread::sleep_for(chrono::milliseconds(100));}}// count表示打印的個(gè)數(shù)void decrement(int count){for (int i = 0; i < count; i++){{lock_guard<mutex> lock(mx);--num;cout << "--current number: " << num << endl;}this_thread::yield();}}private:// 此處為共享資源int num=999;mutex mx;
};int main()
{Base b;thread t1(&Base::increment,&b,10);thread t2(&Base::decrement, &b, 10);t1.join();t2.join();return 0;
}

lock_guard通過修改發(fā)現(xiàn)代碼被精簡了,而且不用擔(dān)心因?yàn)橥浗怄i而造成程序的死鎖,需要根據(jù)實(shí)際情況選擇最優(yōu)的解決方案。

3.std::recursive_mutex類

遞歸互斥鎖std::recursive_mutex允許同一線程多次獲得互斥鎖,可以用來解決同一線程需要多次獲取互斥量時(shí)死鎖的問題,在下面的例子中使用獨(dú)占非遞歸互斥量會(huì)發(fā)生死鎖:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 互斥鎖
class Base
{
public:// count表示打印的個(gè)數(shù)void increment(int count){for (int i = 0; i < count; i++){mx.lock();++num;cout << "++current number: " << num << endl;mx.unlock();this_thread::sleep_for(chrono::milliseconds(100));}}// count表示打印的個(gè)數(shù)void decrement(int count){for (int i = 0; i < count; i++){{lock_guard<mutex> lock(mx);// 發(fā)生死鎖increment(2);--num;cout << "--current number: " << num << endl;}this_thread::yield();}}private:// 此處為共享資源int num = 999;mutex mx;	//遞歸互斥鎖
};int main()
{Base b;thread t1(&Base::increment, &b, 10);thread t2(&Base::decrement, &b, 10);t1.join();t2.join();return 0;
}

錯(cuò)誤信息
上面的程序中執(zhí)行了decrement( )中對(duì)incremen( )調(diào)用之后,程序就會(huì)發(fā)生死鎖,在decrement( )中已經(jīng)對(duì)互斥鎖加鎖了,繼續(xù)調(diào)用incremen( )函數(shù),已經(jīng)得到互斥鎖所有權(quán)的線程再次獲取這個(gè)互斥鎖的所有權(quán)就會(huì)造成死鎖(在C++中程序會(huì)異常退出,使用C庫函數(shù)會(huì)導(dǎo)致這個(gè)互斥鎖永遠(yuǎn)無法被解鎖,最終阻塞所有的線程)。要解決這個(gè)死鎖的問題,一個(gè)簡單的辦法就是使用遞歸互斥鎖std::recursive_mutex,它允許一個(gè)線程多次獲得互斥鎖的所有權(quán)。修改之后的代碼如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 互斥鎖
class Base
{
public:// count表示打印的個(gè)數(shù)void increment(int count){for (int i = 0; i < count; i++){mx.lock();++num;cout << "++current number: " << num << endl;mx.unlock();this_thread::sleep_for(chrono::milliseconds(100));}}// count表示打印的個(gè)數(shù)void decrement(int count){for (int i = 0; i < count; i++){{lock_guard<recursive_mutex> lock(mx);increment(2);--num;cout << "--current number: " << num << endl;}this_thread::yield();}}private:// 此處為共享資源int num = 999;recursive_mutex mx;	//遞歸互斥鎖
};int main()
{Base b;thread t1(&Base::increment, &b, 10);thread t2(&Base::decrement, &b, 10);t1.join();t2.join();return 0;
}

結(jié)果
雖然遞歸互斥鎖可以解決同一個(gè)互斥鎖頻繁獲取互斥鎖資源的問題,但是還是建議少用,主要原因如下:

  • 使用遞歸互斥鎖的場景往往都是可以簡化的,使用遞歸互斥鎖很容易放縱復(fù)雜邏輯的產(chǎn)生,從而導(dǎo)致bug的產(chǎn)生
  • 遞歸互斥鎖比非遞歸互斥鎖效率要低一些。
  • 遞歸互斥鎖雖然允許同一個(gè)線程多次獲得同一個(gè)互斥鎖的所有權(quán),但最大次數(shù)并未具體說明,一旦超過一定的次數(shù),就會(huì)拋出std::system錯(cuò)誤。

4.std::timed_mutex類

std::timed_mutex是超時(shí)獨(dú)占互斥鎖,主要是在獲取互斥鎖資源時(shí)增加了超時(shí)等待功能,因?yàn)椴恢阔@取鎖資源需要等待多長時(shí)間,為了保證不一直等待下去,設(shè)置了一個(gè)超時(shí)時(shí)長,超時(shí)后線程就可以解除阻塞去做其他事情了。
std::timed_mutex比std::_mutex多了兩個(gè)成員函數(shù):try_lock_for()和try_lock_until():

void lock();
bool try_lock();
void unlock();// std::timed_mutex比std::_mutex多出的兩個(gè)成員函數(shù)
template <class Rep, class Period>bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);template <class Clock, class Duration>bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);
  • try_lock_for函數(shù)是當(dāng)線程獲取不到互斥鎖資源的時(shí)候,讓線程阻塞一定的時(shí)間長度
  • try_lock_until函數(shù)是當(dāng)線程獲取不到互斥鎖資源的時(shí)候,讓線程阻塞到某一個(gè)指定的時(shí)間點(diǎn)
  • 關(guān)于兩個(gè)函數(shù)的返回值:當(dāng)?shù)玫交コ怄i的所有權(quán)之后,函數(shù)會(huì)馬上解除阻塞,返回true,如果阻塞的時(shí)長用完或者到達(dá)指定的時(shí)間點(diǎn)之后,函數(shù)也會(huì)解除阻塞,返回false
    下面的示例程序中為大家演示了std::timed_mutex的使用:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 互斥鎖
class Base
{
public:void work(){while (true) {// 鎖未鎖定--返回值為trueif (mx.try_lock_for(chrono::milliseconds(100))){cout << "count"<<++count<<"  ok 線程ID=" << this_thread::get_id() << endl;// 模擬任務(wù)使用的時(shí)長this_thread::sleep_for(chrono::milliseconds(500));cout << "count" << count << "  ok 線程ID=" << this_thread::get_id()<<endl;// 互斥鎖解鎖mx.unlock();break;}else // 阻塞時(shí)間超時(shí)--返回false{// 模擬任務(wù)使用的時(shí)長this_thread::sleep_for(chrono::milliseconds(50));cout << "not ok 線程ID=" << this_thread::get_id()<<endl;}}}private:int count = 0;	// 共享資源timed_mutex mx;	// 超時(shí)獨(dú)占互斥鎖
};int main()
{Base b;thread t1(&Base::work, &b);thread t2(&Base::work, &b);t1.join();t2.join();return 0;
}

result
**注意:解除阻塞和解鎖是兩個(gè)不同的概念。**從上述的結(jié)果,我們可以看出線程38332獲得互斥鎖的所有權(quán),try_lock_for函數(shù)超過阻塞時(shí)長,返回false,阻塞的另一個(gè)線程1864執(zhí)行else中的任務(wù),由于這個(gè)是while(true)該線程1864重新被阻塞,超過阻塞時(shí)長,又重新執(zhí)行else,直到線程38332解除互斥鎖的所有權(quán),線程1864獲得互斥鎖的所有權(quán),執(zhí)行接下來的過程。

四、線程同步之條件變量

1.生產(chǎn)者-消費(fèi)者模型

條件變量通常用于生產(chǎn)者和消費(fèi)者模型。生產(chǎn)者和消費(fèi)者模型主要應(yīng)用的場景是生產(chǎn)者和消費(fèi)者的速度不匹配。 條件變量能夠幫助我們解決某一類線程(生產(chǎn)者或者消費(fèi)者),互斥鎖能夠幫助我們解決某一類線程因?yàn)槎鄠€(gè)線程訪問共享資源導(dǎo)致的數(shù)據(jù)混亂問題。
生產(chǎn)者和消費(fèi)者模型的組成:

  1. 生產(chǎn)者線程->若干個(gè)
    生產(chǎn)商品或者任務(wù)放入到任務(wù)隊(duì)列中;
    任務(wù)隊(duì)列滿了生產(chǎn)者線程就阻塞,不滿就工作;
    通過一個(gè)生產(chǎn)者的條件變量控制生產(chǎn)者線程阻塞和非阻塞。
  2. 消費(fèi)者線程->若干個(gè)
    讀任務(wù)隊(duì)列,將任務(wù)或者數(shù)據(jù)取出;
    任務(wù)隊(duì)列中有數(shù)據(jù)就有消費(fèi)者,沒有數(shù)據(jù)就阻塞;
    通過一個(gè)消費(fèi)者的條件變量控制消費(fèi)者線程阻塞和非阻塞。
  3. 任務(wù)隊(duì)列->存儲(chǔ)數(shù)據(jù)或者任務(wù),對(duì)應(yīng)一塊內(nèi)存,為了讀寫訪問可以通過一個(gè)數(shù)據(jù)結(jié)構(gòu)維護(hù)這塊內(nèi)存
    數(shù)據(jù)結(jié)構(gòu)的類型可以分為數(shù)組、鏈表,也可以是STL容器(queue\stack\list\vector)
    注意:推薦使用STL中的容器,方便增加刪除以及動(dòng)態(tài)擴(kuò)展等。
    生產(chǎn)者消費(fèi)者模型
    生產(chǎn)者消費(fèi)者模型任務(wù)隊(duì)列類的代碼演示:
#include <queue>
#include <iostream>
#include <thread>
using namespace std;class WorkQueue
{
public:// 添加數(shù)據(jù)void put(const int& task){workQueue.push(task);			// 尾部添加數(shù)據(jù)cout << "添加任務(wù):" << task << ",線程ID:" << this_thread::get_id() << endl;}// 刪除數(shù)據(jù)void take(){int node = workQueue.front();	// 取出頭部數(shù)據(jù)workQueue.pop();cout << "刪除任務(wù):" << node << ",線程ID:" << this_thread::get_id() << endl;}// 判斷任務(wù)隊(duì)列是否滿bool isFull(){return workQueue.size() == maxSize;}// 判斷任務(wù)隊(duì)列是否空bool isEmpty(){return workQueue.size() == 0;}// 當(dāng)前任務(wù)隊(duì)列的尺寸int workSize(){return workQueue.size();}private:int maxSize;	//任務(wù)隊(duì)列的容量std::queue<int> workQueue;	// 任務(wù)隊(duì)列的數(shù)據(jù)結(jié)構(gòu)
};

優(yōu)點(diǎn):線程阻塞相比較線程空循環(huán)或者線程的重新創(chuàng)建來說效率更高,資源消耗更少。

2.條件變量

condition_variable:需要配合std::unique_lockstd::mutex進(jìn)行wait操作,也就是阻塞線程的操作。
condition_variable_any:可以和任意帶有l(wèi)ock()、unlock()語義的mutex搭配使用,也就是說有四種:

  • std::mutex:獨(dú)占的非遞歸互斥鎖
  • std::timed_mutex:帶超時(shí)的獨(dú)占非遞歸互斥鎖
  • std::recursive_mutex:不帶超時(shí)功能的遞歸互斥鎖
  • std::recursive_timed_mutex:帶超時(shí)的遞歸互斥鎖

條件變量通常用于生產(chǎn)者和消費(fèi)者模型,大致使用過程如下:

  • 擁有條件變量的線程獲取互斥量
  • 循環(huán)檢查某個(gè)條件,如果條件不滿足阻塞當(dāng)前線程,否則線程繼續(xù)向下執(zhí)行
    產(chǎn)品的數(shù)量達(dá)到上限,生產(chǎn)者阻塞,否則生產(chǎn)者一直生產(chǎn)。。。
    產(chǎn)品的數(shù)量為零,消費(fèi)者阻塞,否則消費(fèi)者一直消費(fèi)。。。
  • 條件滿足之后,可以調(diào)用notify_one()或者notify_all()喚醒一個(gè)或者所有被阻塞的線程
    由消費(fèi)者喚醒被阻塞的生產(chǎn)者,生產(chǎn)者解除阻塞繼續(xù)生產(chǎn)。。。
    由生產(chǎn)者喚醒被阻塞的消費(fèi)者,消費(fèi)者解除阻塞繼續(xù)消費(fèi)。。。

2.1 成員函數(shù)

condition_variable的成員函數(shù)主要分為兩部分:線程等待(阻塞)函數(shù) 和線程通知(喚醒)函數(shù),這些函數(shù)被定義于頭文件 <condition_variable>

  • 等待函數(shù)
    調(diào)用wait()函數(shù)的線程會(huì)被阻塞
// ①	調(diào)用該函數(shù)的線程直接被阻塞
void wait (unique_lock<mutex>& lck);
// ②	該函數(shù)的第二個(gè)參數(shù)是一個(gè)判斷條件,是一個(gè)返回值為布爾類型的函數(shù)
// 		該參數(shù)可以傳遞一個(gè)有名函數(shù)的地址,也可以直接指定一個(gè)匿名函數(shù)
//		表達(dá)式返回false當(dāng)前線程被阻塞,表達(dá)式返回true當(dāng)前線程不會(huì)被阻塞,繼續(xù)向下執(zhí)行
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);

獨(dú)占的互斥鎖對(duì)象不能直接傳遞給wait()函數(shù),需要通過模板類unique_lock進(jìn)行二次處理,通過得到的對(duì)象仍然可以對(duì)獨(dú)占的互斥鎖對(duì)象做(lock、try_lock、try_lock_for、try_lock_until、unlock)操作,使用起來更靈活。
如果線程被該函數(shù)阻塞,這個(gè)線程會(huì)釋放占有的互斥鎖的所有權(quán),當(dāng)阻塞解除之后這個(gè)線程會(huì)重新得到互斥鎖的所有權(quán),繼續(xù)向下執(zhí)行。(wait函數(shù)內(nèi)部實(shí)現(xiàn),其目的是為了避免線程的死鎖)
wait_for()函數(shù)和wait()的功能是一樣的,只不過多了一個(gè)阻塞時(shí)長,假設(shè)阻塞的線程沒有被其他線程喚醒,當(dāng)阻塞時(shí)長用完之后,線程就會(huì)自動(dòng)解除阻塞,繼續(xù)向下執(zhí)行。

template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time);template <class Rep, class Period, class Predicate>
bool wait_for(unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time, Predicate pred);

wait_until()函數(shù)和wait_for()的功能是一樣的,它是指定讓線程阻塞到某一個(gè)時(shí)間點(diǎn),假設(shè)阻塞的線程沒有被其他線程喚醒,當(dāng)?shù)竭_(dá)指定的時(shí)間點(diǎn)之后,線程就會(huì)自動(dòng)解除阻塞,繼續(xù)向下執(zhí)行。

template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time);template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
  • 通知函數(shù)
void notify_one() noexcept;
void notify_all() noexcept;

notify_one():喚醒一個(gè)被當(dāng)前條件變量阻塞的線程
notify_all():喚醒全部被當(dāng)前條件變量阻塞的線程

生產(chǎn)者線程:當(dāng)任務(wù)隊(duì)列滿的時(shí)候,阻塞生產(chǎn)者線程;消費(fèi)者線程:當(dāng)任務(wù)隊(duì)列空的時(shí)候,阻塞消費(fèi)者線程。因此,需要兩個(gè)條件變量控制生產(chǎn)者線程和消費(fèi)者線程阻塞。

#include <queue>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;class WorkQueue
{
public:// 添加數(shù)據(jù)void put(const int& task){unique_lock<mutex> locker(mx);// 如果任務(wù)隊(duì)列滿了,就需要阻塞生產(chǎn)者線程while(workQueue.size()==maxSize){// unique_lock對(duì)象類似lock_guard,管理mutex的加鎖和解鎖,當(dāng)線程訪問到這個(gè)對(duì)象時(shí),未加鎖則加鎖,反之阻塞// 如果locker析構(gòu)了,自動(dòng)對(duì)mx管理的線程所有權(quán)進(jìn)行解鎖// 假設(shè)現(xiàn)在有線程A,B,C,線程A獲得了互斥鎖資源,則線程B,C進(jìn)行阻塞notFull.wait(locker);	// 線程A在運(yùn)行到wait的位置會(huì)先釋放擁有的互斥鎖資源(解鎖),此時(shí),線程B,C從阻塞態(tài)變成就緒態(tài),線程A再阻塞,所以不會(huì)出現(xiàn)死鎖// 等到線程A從wait位置解除阻塞,會(huì)跟其他線程再去搶互斥鎖資源}workQueue.push(task);			// 尾部添加數(shù)據(jù)cout << "添加任務(wù):" << task << ",線程ID:" << this_thread::get_id() << endl;// 喚醒消費(fèi)者notEmpty.notify_one();}// 刪除數(shù)據(jù)void take(){unique_lock<mutex> locker(mx);// 如果任務(wù)隊(duì)列空了,就需要阻塞消費(fèi)者線程while(workQueue.size()==0){notEmpty.wait(locker);}int node = workQueue.front();	// 取出頭部數(shù)據(jù)workQueue.pop();cout << "刪除任務(wù):" << node << ",線程ID:" << this_thread::get_id() << endl;// 喚醒生產(chǎn)者notFull.notify_one();}// 判斷任務(wù)隊(duì)列是否滿bool isFull(){// 任務(wù)隊(duì)列的訪問所以加鎖std::lock_guard<std::mutex> locker(mx);return workQueue.size() == maxSize;}// 判斷任務(wù)隊(duì)列是否空bool isEmpty(){std::lock_guard<std::mutex> locker(mx);return workQueue.size() == 0;}// 當(dāng)前任務(wù)隊(duì)列的尺寸int workSize(){std::lock_guard<std::mutex> locker(mx);return workQueue.size();}private:int maxSize=100;	//任務(wù)隊(duì)列的容量std::queue<int> workQueue;	// 任務(wù)隊(duì)列的數(shù)據(jù)結(jié)構(gòu)mutex mx;						// 獨(dú)占的互斥鎖condition_variable notFull;		// 控制生產(chǎn)者線程condition_variable notEmpty;	// 控制消費(fèi)者線程
};int main()
{thread t1[5];thread t2[5];WorkQueue workQ;for (int i = 0; i < 5; i++){t1[i] = thread(&WorkQueue::put, &workQ, 100 + i);t2[i] = thread(&WorkQueue::take, &workQ);}for (int i = 0; i < 5; i++){t1[i].join();t2[i].join();}return 0;
}

在這里插入圖片描述
條件變量condition_variable類的wait()還有一個(gè)重載的方法,可以接受一個(gè)條件,這個(gè)條件也可以是一個(gè)返回值為布爾類型的函數(shù),條件變量會(huì)先檢查判斷這個(gè)條件是否滿足,如果滿足條件(布爾值為true),則當(dāng)前線程重新獲得互斥鎖的所有權(quán),結(jié)束阻塞,繼續(xù)向下執(zhí)行;如果不滿足條件(布爾值為false),當(dāng)前線程會(huì)釋放互斥鎖(解鎖)同時(shí)被阻塞,等待被喚醒。
優(yōu)化后的代碼:

#include <queue>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;class WorkQueue
{
public:// 添加數(shù)據(jù)void put(const int& task){unique_lock<mutex> locker(mx);notFull.wait(locker, [&]() {return !(workQueue.size() == maxSize);});workQueue.push(task);			// 尾部添加數(shù)據(jù)cout << "添加任務(wù):" << task << ",線程ID:" << this_thread::get_id() << endl;// 喚醒消費(fèi)者notEmpty.notify_one();}// 刪除數(shù)據(jù)void take(){unique_lock<mutex> locker(mx);notEmpty.wait(locker, [&]() {return !workQueue.size() == 0;});int node = workQueue.front();	// 取出頭部數(shù)據(jù)workQueue.pop();cout << "刪除任務(wù):" << node << ",線程ID:" << this_thread::get_id() << endl;// 喚醒生產(chǎn)者notFull.notify_one();}// 判斷任務(wù)隊(duì)列是否滿bool isFull(){// 任務(wù)隊(duì)列的訪問所以加鎖std::lock_guard<std::mutex> locker(mx);return workQueue.size() == maxSize;}// 判斷任務(wù)隊(duì)列是否空bool isEmpty(){std::lock_guard<std::mutex> locker(mx);return workQueue.size() == 0;}// 當(dāng)前任務(wù)隊(duì)列的尺寸int workSize(){std::lock_guard<std::mutex> locker(mx);return workQueue.size();}private:int maxSize=100;	//任務(wù)隊(duì)列的容量std::queue<int> workQueue;	// 任務(wù)隊(duì)列的數(shù)據(jù)結(jié)構(gòu)mutex mx;						// 獨(dú)占的互斥鎖condition_variable notFull;		// 控制生產(chǎn)者線程condition_variable notEmpty;	// 控制消費(fèi)者線程
};int main()
{thread t1[50];thread t2[50];WorkQueue workQ;for (int i = 0; i < 50; i++){t2[i] = thread(&WorkQueue::take, &workQ);t1[i] = thread(&WorkQueue::put, &workQ, 100 + i);}for (int i = 0; i < 50; i++){t1[i].join();t2[i].join();}return 0;
}

總結(jié)

本文詳細(xì)介紹了C++11多線程中thread線程類、mutex等互斥鎖、以及線程同步和生產(chǎn)者消費(fèi)者模型,并提供了對(duì)應(yīng)的示例,本文主要是參考愛編程的大丙,歡迎大家閱讀大丙老師的文章,本文作個(gè)人學(xué)習(xí)之用。

http://aloenet.com.cn/news/28035.html

相關(guān)文章:

  • 做網(wǎng)站的外包能學(xué)到什么磁力蜘蛛
  • 大連網(wǎng)站建設(shè)seo怎么去優(yōu)化
  • 重慶市衛(wèi)生健康委員會(huì)福州短視頻seo網(wǎng)站
  • 天津重型網(wǎng)站建設(shè)推薦沈陽網(wǎng)站seo公司
  • 國內(nèi)外網(wǎng)站建設(shè)長沙關(guān)鍵詞優(yōu)化平臺(tái)
  • 手機(jī)打開網(wǎng)站自動(dòng)跳轉(zhuǎn)網(wǎng)站優(yōu)化排名推薦
  • 做生意必定紅火的公司名字win10優(yōu)化
  • 網(wǎng)站favicon.ico 大小做網(wǎng)站用什么軟件
  • 做網(wǎng)站公司名字應(yīng)該用圖片嗎網(wǎng)站seo優(yōu)化工具
  • 聯(lián)通營業(yè)廳做網(wǎng)站維護(hù)知乎營銷平臺(tái)
  • 東莞網(wǎng)站維護(hù)網(wǎng)站制作費(fèi)用一覽表
  • 廣安網(wǎng)站制作設(shè)計(jì)掃描圖片找原圖
  • 濟(jì)寧網(wǎng)站建設(shè)關(guān)鍵字是什么意思
  • 企業(yè)管理軟件是什么杭州seo博客
  • 微信網(wǎng)站制作價(jià)格競價(jià)防惡意點(diǎn)擊
  • 做爰視頻無風(fēng)險(xiǎn)網(wǎng)站廣告有限公司
  • 服裝店網(wǎng)站模板網(wǎng)站快速建站
  • 深圳龍崗網(wǎng)站維護(hù)阿里域名購買網(wǎng)站
  • 兩個(gè)人能用的一個(gè)公司做網(wǎng)站嗎怎樣注冊(cè)網(wǎng)站免費(fèi)注冊(cè)
  • 貴陽網(wǎng)站建設(shè)制作成都最新消息今天
  • 做網(wǎng)站公司在深圳谷歌瀏覽器官方app下載
  • 安徽建設(shè)行業(yè)安全協(xié)會(huì)網(wǎng)站友情鏈接交易平臺(tái)
  • 社區(qū)電商網(wǎng)站設(shè)計(jì)磁力搜索引擎
  • 寧波公司網(wǎng)站開發(fā)推廣營銷是什么
  • 為什么做視頻網(wǎng)站違法礦產(chǎn)網(wǎng)站建設(shè)價(jià)格
  • 阿里云wordpress很慢找seo外包公司需要注意什么
  • 心雨在線高端網(wǎng)站建設(shè)關(guān)鍵詞分析軟件
  • 成都官方網(wǎng)站建設(shè)seo怎么做關(guān)鍵詞排名
  • 建立淘寶客網(wǎng)站福州短視頻seo網(wǎng)紅
  • 做外貿(mào)的網(wǎng)站怎么建立武漢seo網(wǎng)站排名優(yōu)化