溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

C++11線程、互斥量及條件變量怎么創(chuàng)建

發(fā)布時(shí)間:2023-03-15 10:06:53 來(lái)源:億速云 閱讀:92 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇“C++11線程、互斥量及條件變量怎么創(chuàng)建”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“C++11線程、互斥量及條件變量怎么創(chuàng)建”文章吧。

    前言

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

    1、創(chuàng)建第一個(gè)線程

    //創(chuàng)建線程需要引入頭文件thread
    #include<thread>  
    #include<iostream>
    
    void ThreadMain()
    {
    	cout << "begin thread main" << endl;
    }
    
    int main()
    {
    	//創(chuàng)建新線程t并啟動(dòng)
    	thread t(ThreadMain);
    	//主線程(main線程)等待t執(zhí)行完畢
    	if (t.joinable()) //必不可少
    	{
    		//等待子線程退出
    		t.join();	//必不可少
    	}
    	return 0;
    }

    我們都知道,對(duì)于一個(gè)單線程來(lái)說(shuō),也就main線程或者叫做主線程,所有的工作都是由main線程去完成的。而在多線程環(huán)境下,子線程可以分擔(dān)main線程的工作壓力,在多個(gè)CPU下,實(shí)現(xiàn)真正的并行操作。
    在上述代碼中,可以看到main線程創(chuàng)建并啟動(dòng)了一個(gè)新線程t,由新線程t去執(zhí)行ThreadMain()函數(shù),jion函數(shù)將會(huì)把main線程阻塞住,知道新線程t執(zhí)行結(jié)束,如果新線程t有返回值,返回值將會(huì)被忽略

    我們可以通過(guò)函數(shù)this_thread::get_id()來(lái)判斷是t線程還是main線程執(zhí)行任務(wù)

    void ThreadMain()
    {
    	cout << "線程" << this_thread::get_id()<< ":begin thread main" << endl;
    }
    
    int main()
    {
    	//創(chuàng)建新線程t并啟動(dòng)
    	thread t(ThreadMain);
    	//主線程(main線程)等待t執(zhí)行完畢
    	if (t.joinable()) //必不可少
    	{
    		//等待子線程退出
    		cout << "線程" << this_thread::get_id() << ":正在等待" << endl;
    		t.join();	//必不可少
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    2、線程對(duì)象的生命周期、等待和分離

    void func()
    {
    	cout << "do func" << endl;
    }
    
    int main()
    {
    	thread t(func);
    	return 0;
    }

    上訴代碼運(yùn)行可能會(huì)拋出異常,因?yàn)榫€程對(duì)象t可能先于線程函數(shù)func結(jié)束,應(yīng)該保證線程對(duì)象的生命周期在線程函數(shù)func執(zhí)行完時(shí)仍然存在

    為了防止線程對(duì)象的生命周期早于線程函數(shù)fun結(jié)束,可以使用線程等待join

    void func()
    {
    	while (true)
    	{
    		cout << "do work" << endl;
    		this_thread::sleep_for(std::chrono::seconds(1));//當(dāng)前線程睡眠1秒
    	}
    }
    
    int main()
    {
    	thread t(func);
    	if (t.joinable())
    	{
    		t.join();//main線程阻塞
    	}
    	return 0;
    }

    雖然使用join能有效防止程序的崩潰,但是在某些情況下,我們并不希望main線程通過(guò)join被阻塞在原地,此時(shí)可以采用detach進(jìn)行線程分離。但是需要注意:detach之后main線程就無(wú)法再和子線程發(fā)生聯(lián)系了,比如detach之后就不能再通過(guò)join來(lái)等待子線程,子線程任何執(zhí)行完我們也無(wú)法控制了

    void func()
    {
    	int count = 0;
    	while (count < 3)
    	{
    		cout << "do work" << endl;
    		count++;
    		this_thread::sleep_for(std::chrono::seconds(1));//當(dāng)前線程睡眠1秒
    	}
    }
    
    int main()
    {
    	thread t(func);
    	t.detach();
    	this_thread::sleep_for(std::chrono::seconds(1));//當(dāng)前線程睡眠1秒
    	cout << "線程t分離成功" << endl;
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    3、線程創(chuàng)建的多種方式

    線程的創(chuàng)建和執(zhí)行,無(wú)非是給線程指定一個(gè)入口函數(shù)嘛,例如main線程的入口函數(shù)就main()函數(shù),前面編寫(xiě)的子線程的入口函數(shù)是一個(gè)全局函數(shù)。除了這些之外線程的入口函數(shù)還可以是函數(shù)指針、仿函數(shù)、類(lèi)的成員函數(shù)、lambda表達(dá)式等,它們都有一個(gè)共同的特點(diǎn):都是可調(diào)用對(duì)象。線程的入口函數(shù)指定,可以為任意一個(gè)可調(diào)用對(duì)象。

    普通函數(shù)作為線程的入口函數(shù)

    void func()
    {
    	cout << "hello world" << endl;
    }
    
    int main()
    {
    	thread t(func);
    	if (t.joinable())
    	{
    		t.join();
    	}
    	return 0;
    }

    類(lèi)的成員函數(shù)作為線程的入口函數(shù)

    class ThreadMain
    {
    public:
    	ThreadMain() {}
    	virtual ~ThreadMain(){}
    	void SayHello(std::string name)
    	{
    		cout << "hello " << name << endl;
    	}
    };
    
    int main()
    {
    	ThreadMain obj;
    	thread t(&ThreadMain::SayHello, obj, "fl");
    	thread t1(&ThreadMain::SayHello, &obj, "fl");
    	t.join();
    	t1.join();
    	return 0;
    }

    t和t1在傳遞參數(shù)時(shí)存在不同:

    • t是用對(duì)象obj調(diào)用線程函數(shù)的語(yǔ)句,即線程函數(shù)將在obj對(duì)象的上下文中運(yùn)行。這里obj是通過(guò)值傳遞給線程構(gòu)造函數(shù)的,因此在線程中使用的是對(duì)象obj的一個(gè)副本。這種方式適用于類(lèi)定義在局部作用域中時(shí),需要將其傳遞給線程的情況。

    • t1是使用對(duì)象的指針&obj調(diào)用線程函數(shù)的語(yǔ)句,即線程函數(shù)將在對(duì)象obj的指針?biāo)赶虻纳舷挛闹羞\(yùn)行。這里使用的是對(duì)象obj的指針,因此在線程中使用的是原始的obj對(duì)象。這種方式適用于類(lèi)定義在全局或靜態(tài)作用域中時(shí),需要將其傳遞給線程的情況。

    如果需要在類(lèi)的成員函數(shù)中,創(chuàng)建線程,以類(lèi)中的另一個(gè)成員函數(shù)作為入口函數(shù),再執(zhí)行

    class ThreadMain
    {
    public:
    	ThreadMain() {}
    	virtual ~ThreadMain(){}
    	void SayHello(std::string name)
    	{
    		cout << "hello " << name << endl;
    	}
    	void asycSayHello(std::string name)
    	{
    		thread t(&ThreadMain::SayHello, this, name);
    		if (t.joinable())
    		{
    			t.join();
    		}
    	}
    };
    
    int main()
    {
    	ThreadMain obj;
    	obj.asycSayHello("fl");
    	return 0;
    }

    在asycSayHello的成員函數(shù)中,如果沒(méi)有傳遞this指針,會(huì)導(dǎo)致編譯不通過(guò)

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    原因就是參數(shù)列表不匹配,因此需要我們顯示的傳遞this指針,表示以本對(duì)象的成員函數(shù)作為參數(shù)的入口函數(shù)

    lambda表達(dá)式作為線程的入口函數(shù)

    int main()
    {
    	thread t([](int i){
    		cout << "test lambda i = " << i << endl;
    	}, 123);
    	if (t.joinable())
    	{
    		t.join();
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    在類(lèi)的成員函數(shù)中,以lambda表達(dá)式作為線程的入口函數(shù)

    class TestLmadba
    {
    public:
    	void Start()
    	{
    		thread t([this](){
    			cout << "name is " << this->name << endl;
    		});
    		if (t.joinable())
    		{
    			t.join();
    		}
    	}
    
    private:
    	std::string name = "fl";
    };
    
    int main()
    {
    	TestLmadba test;
    	test.Start();
    	return 0;
    }

    在類(lèi)的成員函數(shù)中,以lambda表達(dá)式作為線程的入口函數(shù),如果需要訪問(wèn)兌現(xiàn)的成員變量,也需要傳遞this指針

    仿函數(shù)作為線程的入口函數(shù)

    class Mybusiness
    {
    public:
    	Mybusiness(){}
    	virtual ~Mybusiness(){}
    
    	void operator()(void)
    	{
    		cout << "Mybusiness thread id is " << this_thread::get_id() << endl;
    	}
    
    	void operator()(string name)
    	{
    		cout << "name is " << name << endl;
    	}
    };
    
    int main()
    {
    	Mybusiness mb;
    	thread t(mb);
    	if (t.joinable())
    	{
    		t.join();
    	}
    	thread t1(mb, "fl");
    	if (t1.joinable())
    	{
    		t1.join();
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    線程t以無(wú)參的仿函數(shù)作為函數(shù)入口,而線程t1以有參的仿函數(shù)作為函數(shù)入口

    函數(shù)指針作為線程的入口函數(shù)

    void func()
    {
    	cout << "thread id is " << this_thread::get_id() << endl;
    }
    
    void add(int a, int b)
    {
    	cout << a << "+" << b << "=" << a + b << endl;
    }
    
    int main()
    {	
    	//采用C++11擴(kuò)展的using來(lái)定義函數(shù)指針類(lèi)型
    	using FuncPtr = void(*)();
    	using FuncPtr1 = void(*)(int, int);
    	//使用FuncPtr來(lái)定義函數(shù)指針變量
    	FuncPtr ptr = &func;
    	thread t(ptr);
    	if (t.joinable())
    	{
    		t.join();
    	}
    
    	FuncPtr1 ptr1 = add;
    	thread t1(ptr1, 1, 10);
    	if (t1.joinable())
    	{
    		t1.join();
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    function和bind作為線程的入口函數(shù)

    void func(string name)
    {
    	cout << this_thread::get_id() << ":name is " << name << endl;
    }
    
    int main()
    {
    	function<void(string)> f(func);
    	thread t(f, "fl");
    	if (t.joinable())
    	{
    		t.join();
    	}
    	thread t1(bind(func, "fl"));
    	if (t1.joinable())
    	{
    		t1.join();
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    線程不能拷貝和復(fù)制,但可以移動(dòng)

    //賦值操作
    void func(string name)
    {
    	cout << this_thread::get_id() << ":name is " << name << endl;
    }
    
    int main()
    {
    	thread t1(func, "fl");
    	thread t2 = t1;
    	thread t3(t1);
    	return 0;
    }

    編譯報(bào)錯(cuò):

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    在線程內(nèi)部,已經(jīng)將線程的賦值和拷貝操作delete掉了,所以無(wú)法調(diào)用到

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    //移動(dòng)操作
    void func(string name)
    {
    	cout << this_thread::get_id() << ":name is " << name << endl;
    }
    
    int main()
    {
    	thread t1(func, "fl");
    	thread t2(std::move(t1));
    	if (t1.joinable())
    	{
    		t1.join();
    	}
    	if (t2.joinable())
    	{
    		t2.join();
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    線程被移動(dòng)之后,線程對(duì)象t1將不代表任何線程了,可以通過(guò)調(diào)試觀察到

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    4、互斥量

    當(dāng)多個(gè)線程同時(shí)訪問(wèn)同一個(gè)共享資源時(shí),如果不加以保護(hù)或者不做任何同步操作,可能出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)或不一致的狀態(tài),導(dǎo)致程序運(yùn)行出現(xiàn)問(wèn)題。
    為了保證所有的線程都能夠正確地、可預(yù)測(cè)地、不產(chǎn)生沖突地訪問(wèn)共享資源,C++11提供了互斥量。
    互斥量是一種同步原語(yǔ),是一種線程同步手段,用來(lái)保護(hù)多線程同時(shí)訪問(wèn)的共享數(shù)據(jù)。互斥量就是我們平常說(shuō)的鎖
    C++11中提供了4種語(yǔ)義的互斥量

    • std::mutex:獨(dú)占的互斥量,不能遞歸

    • std::timed_mutex:帶超時(shí)的獨(dú)占互斥量,不能遞歸使用

    • std::recursive_mutex:遞歸互斥量,不能帶超時(shí)功能

    • std::recursive_timed_mutex:帶超時(shí)的遞歸互斥量

    4.1 獨(dú)占的互斥量std::mutex

    這些互斥量的接口基本類(lèi)似,一般用法是通過(guò)lock()方法來(lái)阻塞線程,知道獲得互斥量的所有權(quán)為止。在線程獲得互斥量并完成任務(wù)之后,就必須使用unlock()來(lái)解除對(duì)互斥量的占用,lock()和unlock()必須成對(duì)出現(xiàn)。try_lock()嘗試鎖定互斥量,如果成功則返回true,失敗則返回false,它是非阻塞的。

    int num = 0;
    std::mutex mtx;
    void func()
    {
    	for (int i = 0; i < 100; ++i)
    	{
    		mtx.lock();
    		num++;
    		mtx.unlock();
    	}
    }
    
    int main()
    {
    	thread t1(func);
    	thread t2(func);
    	if (t1.joinable())
    	{
    		t1.join();
    	}
    	if (t2.joinable())
    	{
    		t2.join();
    	}
    	cout << num << endl;
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    使用lock_guard可以簡(jiǎn)化lock/unlock的寫(xiě)法,同時(shí)也更安全,因?yàn)閘ock_guard在構(gòu)造時(shí)會(huì)自動(dòng)鎖定互斥量,而在退出作用域后進(jìn)行析構(gòu)時(shí)自動(dòng)解鎖,從而保證了互斥量的正確操作,避免忘記unlock操作,因此,盡量用lock_guard。lock_guard用到了RALL技術(shù),這種技術(shù)在類(lèi)的構(gòu)造函數(shù)中分配資源,在析構(gòu)函數(shù)中釋放資源,保證資源在出了作用域之后就釋放。上面的例子使用lock_guard后會(huì)更簡(jiǎn)介,代碼如下:

    void func()
    {
    	for (int i = 0; i < 100; ++i)
    	{
    		lock_guard<mutex> lock(mtx);
    		num++;
    	}
    }

    一般來(lái)說(shuō),當(dāng)某個(gè)線程執(zhí)行操作完畢后,釋放鎖,然后需要等待幾十毫秒,讓其他線程也去獲取鎖資源,也去執(zhí)行操作。如果不進(jìn)行等待的話,可能當(dāng)前線程釋放鎖后,又立馬獲取了鎖資源,會(huì)導(dǎo)致其他線程出現(xiàn)饑餓。

    4.2 遞歸獨(dú)占互斥量recursive_mutex

    遞歸鎖允許同一線程多次獲得該互斥鎖,可以用來(lái)解決同一線程需要多次獲取互斥量時(shí)死鎖的問(wèn)題。在以下代碼中,一個(gè)線程多次獲取同一個(gè)互斥量時(shí)會(huì)發(fā)生死鎖

    class Complex
    {
    public:
    	std::mutex mtx;
    	void SayHello()
    	{
    		lock_guard<mutex> lock(mtx);
    		cout << "Say Hello" << endl;
    		SayHi();
    	}
    
    	void SayHi()
    	{
    		lock_guard<mutex> lock(mtx);
    		cout << "say Hi" << endl;
    	}
    };
    
    int main()
    {
    	Complex complex;
    	complex.SayHello();
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    這個(gè)例子運(yùn)行起來(lái)就發(fā)生了死鎖,因?yàn)樵谡{(diào)用SayHello時(shí)獲取了互斥量,之后再調(diào)用SayHI又要獲取相同的互斥量,但是這個(gè)互斥量已經(jīng)被當(dāng)前線程獲取 ,無(wú)法釋放,這時(shí)就會(huì)產(chǎn)生死鎖,導(dǎo)致程序崩潰。
    要解決這里的死鎖問(wèn)題,最簡(jiǎn)單的方法就是采用遞歸鎖:std::recursive_mutex,它允許同一個(gè)線程多次獲取互斥量

    class Complex
    {
    public:
    	std::recursive_mutex mtx;//同一線程可以多次獲取同一互斥量,不會(huì)發(fā)生死鎖
    	void SayHello()
    	{
    		lock_guard<recursive_mutex> lock(mtx);
    		cout << "Say Hello" << endl;
    		SayHi();
    	}
    
    	void SayHi()
    	{
    		lock_guard<recursive_mutex> lock(mtx);
    		cout << "say Hi" << endl;
    	}
    };

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    需要注意的是盡量不要使用遞歸鎖比較好,主要原因如下:

    1、需要用到遞歸鎖定的多線程互斥量處理往往本身就是可以簡(jiǎn)化的,允許遞歸互斥量很容易放縱復(fù)雜邏輯的產(chǎn)生,而非導(dǎo)致一些多線程同步引起的晦澀問(wèn)題
    2、遞歸鎖的效率比非遞歸鎖的效率低
    3、遞歸鎖雖然允許同一個(gè)線程多次獲得同一個(gè)互斥量,但可重復(fù)的最大次數(shù)并為具體說(shuō)明,一旦超過(guò)一定次數(shù),再對(duì)lock進(jìn)行調(diào)用就會(huì)拋出std::system錯(cuò)誤

    4.3 帶超時(shí)的互斥量std::timed_mutex和std::recursive_timed_mutex

    std::timed_mutex是超時(shí)的獨(dú)占鎖,srd::recursive_timed_mutex是超時(shí)的遞歸鎖,主要用在獲取鎖時(shí)增加超時(shí)鎖等待功能,因?yàn)橛袝r(shí)不知道獲取鎖需要多久,為了不至于一直在等待獲互斥量,就設(shè)置一個(gè)等待超時(shí)時(shí)間,在超時(shí)時(shí)間后還可做其他事。
    std::timed_mutex比std::mutex多了兩個(gè)超時(shí)獲取鎖的接口:try_lock_for和try_lock_until,這兩個(gè)接口是用來(lái)設(shè)置獲取互斥量的超時(shí)時(shí)間,使用時(shí)可以用一個(gè)while循環(huán)去不斷地獲取互斥量。

    std::timed_mutex mtx;
    
    void work()
    {
    	chrono::milliseconds timeout(100);
    	while (true)
    	{
    		if (mtx.try_lock_for(timeout))
    		{
    			cout << this_thread::get_id() << ": do work with the mutex" << endl;
    			this_thread::sleep_for(chrono::milliseconds(250));
    			mtx.unlock();
    		}
    		else
    		{
    			cout << this_thread::get_id() << ": do work without the mutex" << endl;
    			this_thread::sleep_for(chrono::milliseconds(100));
    		}
    	}
    }
    
    int main()
    {
    	thread t1(work);
    	thread t2(work);
    	if (t1.joinable())
    	{
    		t1.join();
    	}
    	if (t2.joinable())
    	{
    		t2.join();
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    在上面的例子中,通過(guò)一個(gè)while循環(huán)不斷地去獲取超時(shí)鎖,如果超時(shí)還沒(méi)有獲取到鎖時(shí)就休眠100毫秒,再繼續(xù)獲取鎖。
    相比std::timed_mutex,std::recursive_timed_mutex多了遞歸鎖的功能,允許同一個(gè)線程多次獲得互斥量。std::recursive_timed_mutex和std::recursive_mutex的用法類(lèi)似,可以看作在std::recursive_mutex的基礎(chǔ)上增加了超時(shí)功能

    4.4 std::lock_guard和std::unique_lock

    lock_guard和unique_lock的功能完全相同,主要差別在于unique_lock更加靈活,可以自由的釋放mutex,而lock_guard需要等到生命周期結(jié)束后才能釋放。

    它們的構(gòu)造函數(shù)中都有第二個(gè)參數(shù)
    unique_lock:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    lock_guard:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    可以從源碼中看到,unique_lock的構(gòu)造函數(shù)中,第二個(gè)參數(shù)的種類(lèi)有三種,分別是adopt_lock,defer_lock和try_to_lock。lock_guard的構(gòu)造函數(shù)中,第二個(gè)參數(shù)的種類(lèi)只有一種,adopt_lock

    這些參數(shù)的含義分別是:
    adopt_lock:互斥量已經(jīng)被lock,構(gòu)造函數(shù)中無(wú)需再lock(lock_ guard與unique_lock通用)
    defer_lock:互斥量稍后我會(huì)自行l(wèi)ock,不需要在構(gòu)造函數(shù)中l(wèi)ock,只初始化一個(gè)沒(méi)有加鎖的mutex
    try_to_lock:主要作用是在不阻塞線程的情況下嘗試獲取鎖,如果互斥量當(dāng)前未被鎖定,則返回std::unique_lock對(duì)象,該對(duì)象擁有互斥量并且已經(jīng)被鎖定。如果互斥量當(dāng)前已經(jīng)被另一個(gè)線程鎖定,則返回一個(gè)空的std::unique_lock對(duì)象

    mutex mtx;
    void func()
    {
    	//mtx.lock();//需要加鎖,否則在lock的生命周期結(jié)束后,會(huì)自動(dòng)解鎖,則會(huì)導(dǎo)致程序崩潰
    	unique_lock<mutex> lock(mtx, std::adopt_lock);
    	cout << this_thread::get_id() << " do work" << endl;
    }
    
    int main()
    {
    	thread t(func);
    	if (t.joinable())
    	{
    		t.join();
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    adopt_lock就表示構(gòu)造unique_lock<mutex>時(shí),認(rèn)為mutex已經(jīng)加過(guò)鎖了,就不會(huì)再加鎖了,它就把加鎖的權(quán)限和時(shí)機(jī)交給了我們,由我們自己控制

    mutex mtx;
    void func()
    {
    	while (true)
    	{
    		unique_lock<mutex> lock(mtx, std::defer_lock);
    		cout << "func thread id is " << this_thread::get_id() << endl;
    		this_thread::sleep_for(chrono::milliseconds(500));
    	}
    }
    
    int main()
    {
    	thread t1(func);
    	thread t2(func);
    	if (t1.joinable())
    	{
    		t1.join();
    	}
    	if (t2.joinable())
    	{
    		t2.join();
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    本來(lái)我們的意愿是t1和t2每個(gè)時(shí)刻只能有一個(gè)線程打印"func thread id is&hellip;",但是實(shí)際上卻發(fā)生了競(jìng)爭(zhēng)的關(guān)系,原因就在于defer_lock在構(gòu)造unique_lock<mutex>時(shí),認(rèn)為mutex在后面會(huì)加鎖,也就沒(méi)有加鎖,所以打印結(jié)果才發(fā)生混亂,因此需要我們手動(dòng)改進(jìn)一下

    void func()
    {
    	while (true)
    	{
    		unique_lock<mutex> lock(mtx, std::defer_lock);
    		lock.lock();
    		cout << "func thread id is " << this_thread::get_id() << endl;
    		this_thread::sleep_for(chrono::milliseconds(500));
    		//lock.unlock();  //可以加,也可以不加
    		//因?yàn)閮?nèi)部有一個(gè)標(biāo)準(zhǔn)為,如果我們自己手動(dòng)解鎖了,由于標(biāo)志位的改變,在調(diào)用lock的析構(gòu)函數(shù)時(shí),就不會(huì)進(jìn)行解鎖操作
    	}
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    5、call_once/once_flag的使用

    為了保證在多線程環(huán)境中某個(gè)函數(shù)僅被調(diào)用一次,比如,需要在初始化某個(gè)對(duì)象,而這個(gè)對(duì)象只能初始化一次時(shí),就可以用std::call_once來(lái)保證函數(shù)在多線程環(huán)境中只能被調(diào)用一次。使用std::call_once時(shí),需要一個(gè)once_flag作為call_once的入?yún)ⅲ梅ū容^簡(jiǎn)單

    call_once函數(shù)模板

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    在使用call_once時(shí),第一個(gè)參數(shù)是類(lèi)型為once_flag的標(biāo)志位,第二個(gè)參數(shù)是一個(gè)可調(diào)用對(duì)象,第三個(gè)為可變參數(shù),表示的可調(diào)用對(duì)象中的參數(shù)

    std::once_flag flag;
    
    void do_once()
    {
    	std::call_once(flag, [](){
    		cout << "call once" << endl;
    	});
    }
    
    int main()
    {
    	const int ThreadSize = 5;
    	vector<thread> threads;
    	for (int i = 0; i < ThreadSize; ++i)
    	{
    		threads.emplace_back(do_once);
    	}
    	for (auto& t : threads)
    	{
    		if (t.joinable())
    		{
    			t.join();
    		}
    	}
    	return 0;
    }

    執(zhí)行結(jié)果:

    C++11線程、互斥量及條件變量怎么創(chuàng)建

    6、條件變量

    條件變量是C++11提供的另外一種用于等待的同步機(jī)制,它能夠阻塞一個(gè)或者多個(gè)賢臣,直到收到另一個(gè)線程發(fā)出的通知或者超時(shí),才會(huì)喚醒當(dāng)前阻塞的線程。條件變量需要和互斥量配合起來(lái)使用。C++11提供了兩種條件變量:

    • condition_valuable,配合std::unique<mutex>進(jìn)行wait操作

    • condition_valuable_any,和任意帶有l(wèi)ock,unlock語(yǔ)義的mutex搭配使用,比較靈活,但效率比condition_valuable差一些

    可以看到condition_valuable_any比condition_valuable更靈活,因?yàn)樗ㄓ?,?duì)所有的鎖都適用,而condition_valuable的性能更好。我們應(yīng)該根據(jù)具體的應(yīng)用場(chǎng)景來(lái)選擇合適的條件變量

    條件變量的使用條件如下:

    • 擁有條件變量的線程獲取互斥量

    • 循環(huán)檢測(cè)某個(gè)條件,如果條件不滿(mǎn)足,則阻塞直到條件滿(mǎn)足;如果條件滿(mǎn)足,則向下執(zhí)行

    • 某個(gè)線程滿(mǎn)足條件執(zhí)行完畢之后調(diào)用notify_onc或者notify_all喚醒一個(gè)或者所有等待的線程

    一個(gè)簡(jiǎn)單的生產(chǎn)者消費(fèi)者模型

    mutex mtx;
    condition_variable_any notEmpty;//沒(méi)滿(mǎn)的條件變量
    condition_variable_any notFull;//不為空的條件變量
    list<string> list_; //緩沖區(qū)
    const int custom_threads_size = 3;//消費(fèi)者的數(shù)量
    const int produce_threads_size = 4;//生產(chǎn)者的數(shù)量
    const int max_size = 10;
    
    void produce(int i)
    {
    	while (true)
    	{
    		lock_guard<mutex> lock(mtx);
    		notEmpty.wait(mtx, []{
    			return list_.size() != max_size;
    		});
    		stringstream ss;
    		ss << "生產(chǎn)者" << i << "生產(chǎn)的東西";
    		list_.push_back(ss.str());
    		notFull.notify_one();
    	}
    }
    
    void custome(int i)
    {
    	while (true)
    	{
    		lock_guard<mutex> lock(mtx);
    		notFull.wait(mtx, []{
    			return !list_.empty();
    		});
    		cout << "消費(fèi)者" << i << "消費(fèi)了 " << list_.front() << endl;
    		list_.pop_front();
    		notEmpty.notify_one();
    	}
    }
    
    int main()
    {
    	vector<std::thread> producer;
    	vector<std::thread> customer;
    	for (int i = 0; i < produce_threads_size; ++i)
    	{
    		producer.emplace_back(produce, i);
    	}
    	for (int i = 0; i < custom_threads_size; ++i)
    	{
    		customer.emplace_back(custome, i);
    	}
    
    	for (int i = 0; i < produce_threads_size; ++i)
    	{
    		producer[i].join();
    	}
    	for (int i = 0; i < custom_threads_size; ++i)
    	{
    		customer[i].join();
    	}
    	return 0;
    }

    在上述案例中,list<string> list_是一個(gè)臨界資源,無(wú)論是生產(chǎn)者生產(chǎn)數(shù)據(jù),還是消費(fèi)者消費(fèi)數(shù)據(jù),都要往list_中插入數(shù)據(jù)或者刪除數(shù)據(jù),為了防止出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)或不一致的狀態(tài),導(dǎo)致程序運(yùn)行出現(xiàn)問(wèn)題,因?yàn)槊看尾僮鱨ist_時(shí)都需要進(jìn)行加鎖操作。
    當(dāng)list_沒(méi)有滿(mǎn)的情況下,生產(chǎn)者可以生產(chǎn)數(shù)據(jù),如果滿(mǎn)了,則會(huì)阻塞在條件變量notFull下,需要消費(fèi)者通過(guò)notify_one()隨機(jī)喚醒一個(gè)生產(chǎn)者。
    當(dāng)list_不為空的情況下。消費(fèi)者可以消費(fèi)數(shù)據(jù),如果空了,則會(huì)阻塞在條件變量notEmpty下,需要生產(chǎn)者通過(guò)notify_one()隨機(jī)喚醒一個(gè)消費(fèi)者。

    以上就是關(guān)于“C++11線程、互斥量及條件變量怎么創(chuàng)建”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    c++
    AI