欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++線(xiàn)程間的互斥和通信場(chǎng)景分析

 更新時(shí)間:2021年05月21日 10:31:41   作者:Redamanc  
很多朋友對(duì)C++線(xiàn)程間的互斥和通信知識(shí)掌握不是多牢靠,今天小編通過(guò)模擬車(chē)站賣(mài)票應(yīng)用場(chǎng)景給大家詳細(xì)解析C++線(xiàn)程間的互斥和通信知識(shí),感興趣的朋友跟隨小編一起看看吧

互斥鎖(mutex)

為了更好地理解,互斥鎖,我們可以首先來(lái)看這么一個(gè)應(yīng)用場(chǎng)景:模擬車(chē)站賣(mài)票。

模擬車(chē)站賣(mài)票

場(chǎng)景說(shuō)明:
Yang車(chē)站售賣(mài)從亞特蘭蒂斯到古巴比倫的時(shí)光飛船票;因?yàn)闄C(jī)會(huì)難得,所以票數(shù)有限,一經(jīng)發(fā)售,謝絕補(bǔ)票。
飛船票總數(shù):100張;
售賣(mài)窗口:3個(gè)。
對(duì)于珍貴的飛船票來(lái)說(shuō),這個(gè)資源是互斥的,比如第100張票,只能賣(mài)給一個(gè)人,不可能同時(shí)賣(mài)給兩個(gè)人。3個(gè)窗口都有權(quán)限去售賣(mài)飛船票(唯一合法途徑)。

不加鎖的結(jié)果

根據(jù)場(chǎng)景說(shuō)明,我們可以很快地分析如下:
可以使用三個(gè)線(xiàn)程來(lái)模擬三個(gè)獨(dú)立的窗口同時(shí)進(jìn)行賣(mài)票;
定義一個(gè)全局變量,每當(dāng)一個(gè)窗口賣(mài)出一張票,就對(duì)這個(gè)變量進(jìn)行減減操作。
故寫(xiě)出如下代碼:

#include <iostream>
#include <thread>
#include <list>
using namespace std;

int tickets = 100; // 車(chē)站剩余票數(shù)總數(shù)

void sellTickets(int win)
{
	while (tickets > 0)
	{
		{
			if (tickets > 0)
			{
				cout << "窗口:" << win << " 賣(mài)出了第:" << tickets << "張票!" << endl;
				tickets--;
			}
			std::this_thread::sleep_for(std::chrono::microseconds(400));
		}
	}
}

int main()
{
	list<std::thread> tlist;
	for (int i = 1; i <= 3; ++i)
	{
		tlist.push_back(std::thread(sellTickets, i));
	}
	for (std::thread& t : tlist)
	{
		t.join();
	}
	cout << "所有窗口賣(mài)票結(jié)束!" << endl;

	return 0;
}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

通過(guò)運(yùn)行,我們可以發(fā)現(xiàn)問(wèn)題:
對(duì)于一張票來(lái)說(shuō),賣(mài)出去了多次!
這不白嫖嗎???這合適嗎?
原因也很簡(jiǎn)單,對(duì)于線(xiàn)程來(lái)說(shuō),誰(shuí)先執(zhí)行,誰(shuí)后執(zhí)行,完全是根據(jù)CPU的調(diào)度,根本不可能掌握清楚。
所以,這個(gè)代碼是線(xiàn)程不安全的!
那,怎么解決呢?
當(dāng)然是:互斥鎖了!

加鎖后的結(jié)果

我們對(duì)上述代碼做出如下修改:

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;

int tickets = 100; 
std::mutex mtx;

void sellTickets(int win)
{
	while (tickets > 0)
	{
		{
			lock_guard<std::mutex> lock(mtx);
			if (tickets > 0)
			{
				cout << "窗口:" << win << " 賣(mài)出了第:" << tickets << "張票!" << endl;
				tickets--;
			}
			std::this_thread::sleep_for(std::chrono::microseconds(400));
		}
	}
}

int main()
{
	list<std::thread> tlist;
	for (int i = 1; i <= 3; ++i)
	{
		tlist.push_back(std::thread(sellTickets, i));
	}
	for (std::thread& t : tlist)
	{
		t.join();
	}
	cout << "所有窗口賣(mài)票結(jié)束!" << endl;

	return 0;
}

首先定義了一個(gè)全局的互斥鎖std::mutex mtx;接著在對(duì)票數(shù)tickets進(jìn)行減減操作時(shí),定義了lock_guard,這個(gè)就相當(dāng)于智能指針scoped_ptr一樣,可以出了作用域自動(dòng)釋放鎖資源。
運(yùn)行結(jié)果如下:

在這里插入圖片描述

我們可以看到這一次,就沒(méi)問(wèn)題了。

簡(jiǎn)單總結(jié)

互斥鎖的使用可以有三種:
(首先都需要在全局定義互斥鎖std::mutex mtx

  • 首先可以直接在需要加鎖和解鎖的地方,手動(dòng)進(jìn)行:加鎖mtx.lock()、解鎖mtx.unlock();
  • 可以在需要加鎖的地方定義保護(hù)鎖:lock_guard<std::mutex> lock(mtx),這個(gè)鎖在定義的時(shí)候自動(dòng)上鎖,出了作用域自動(dòng)解鎖。(其實(shí)就是借助了智能指針的思想,定義對(duì)象出調(diào)用構(gòu)造函數(shù)底層調(diào)用lock(),出了作用域調(diào)用析構(gòu)函數(shù)底層調(diào)用unlock());
  • 可以在需要加鎖的地方定義唯一鎖:unique_lock<std::mutex> lock(mtx),這個(gè)鎖和保護(hù)鎖類(lèi)似,但是比保護(hù)鎖更加好用。(可以類(lèi)比智能指針中的scoped_ptrunique_ptr的區(qū)別,二者都是將拷貝構(gòu)造和賦值重載函數(shù)刪除了,但是unique_ptrunique_lock都定義了帶有右值引用的拷貝構(gòu)造和賦值)

條件變量(conditon_variable)

如果說(shuō),互斥鎖是為了解決線(xiàn)程間互斥的問(wèn)題,那么,條件變量就是為了解決線(xiàn)程間通信的問(wèn)題。
同樣的,我們可以首先來(lái)看一個(gè)問(wèn)題(模型):

生產(chǎn)者消費(fèi)者線(xiàn)程模型

生產(chǎn)者消費(fèi)者線(xiàn)程模型是一個(gè)很經(jīng)典的線(xiàn)程模型;
首先會(huì)有兩個(gè)線(xiàn)程,一個(gè)是生產(chǎn)者,一個(gè)是消費(fèi)者,生產(chǎn)者只負(fù)責(zé)生產(chǎn)資源,消費(fèi)者只負(fù)責(zé)消費(fèi)資源。

產(chǎn)生問(wèn)題

根據(jù)上述互斥鎖的理解,我們可以寫(xiě)出如下代碼:

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
using namespace std;

std::mutex mtx; 

class Queue
{
public:
	void put(int num)
	{
		lock_guard<std::mutex> lock(mtx);
		que.push(num);
		cout << "生產(chǎn)者,生產(chǎn)了:" << num << "號(hào)產(chǎn)品" << endl;
	}
	void get()
	{
		lock_guard<std::mutex> lock(mtx);
		int val = que.front();
		que.pop();
		cout << "消費(fèi)者,消費(fèi)了:" << val << "號(hào)產(chǎn)品" << endl;
	}
private:
	queue<int> que;
};

void producer(Queue* que)
{
	for (int i = 0; i < 10; ++i)
	{
		que->put(i);
		std::this_thread::sleep_for(std::chrono::milliseconds(200));
	}
}

void consumer(Queue* que)
{
	for (int i = 0; i < 10; ++i)
	{
		que->get();
		std::this_thread::sleep_for(std::chrono::milliseconds(200));
	}
}
int main()
{
	Queue que;

	std::thread t1(producer, &que);
	std::thread t2(consumer, &que);

	t1.join();
	t2.join();

	return 0;
}

同樣的,我們定義了兩個(gè)線(xiàn)程:t1t2分別作為生產(chǎn)者消費(fèi)者,并且定義了兩個(gè)線(xiàn)程函數(shù)producerconsumer,這兩個(gè)函數(shù)接受一個(gè)Queue*的參數(shù),并且通過(guò)這個(gè)指針調(diào)用putget方法,這兩個(gè)方法就是往資源隊(duì)列里面執(zhí)行入隊(duì)和出隊(duì)操作。
運(yùn)行結(jié)果如下:

在這里插入圖片描述

我們會(huì)發(fā)現(xiàn),出錯(cuò)了。
多運(yùn)行幾次試試:

在這里插入圖片描述
在這里插入圖片描述

我們發(fā)現(xiàn),每次運(yùn)行的結(jié)果還都不一樣,但是都會(huì)出現(xiàn)系統(tǒng)崩潰的問(wèn)題。
仔細(xì)來(lái)看這個(gè)錯(cuò)誤原因:

在這里插入圖片描述

我們?cè)傧胂脒@個(gè)代碼的邏輯:
一個(gè)生產(chǎn)者只負(fù)責(zé)生產(chǎn);
一個(gè)消費(fèi)者只負(fù)責(zé)消費(fèi);
他們共同在隊(duì)列里面存取資源;
存取資源操作本身是互斥的。
發(fā)現(xiàn)問(wèn)題了嗎?
這兩個(gè)線(xiàn)程之間彼此的操作獨(dú)立,換句話(huà)說(shuō),
沒(méi)有通信!
生產(chǎn)者生產(chǎn)的時(shí)候,消費(fèi)者不知道;
消費(fèi)者消費(fèi)的時(shí)候,生產(chǎn)者也不知道;
但是消費(fèi)者是要從隊(duì)列里面取資源的,如果某一個(gè)時(shí)刻,隊(duì)列里為空了,它就不能取了!

解決問(wèn)題

分析完問(wèn)題之后,我們知道了:
問(wèn)題出在:沒(méi)有通信上面。
那么如何解決通信問(wèn)題呢?
當(dāng)然就是:條件變量了!
我們做出如下代碼的修改:

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
using namespace std;

std::mutex mtx; // 互斥鎖,用于線(xiàn)程間互斥
std::condition_variable cv;// 條件變量,用于線(xiàn)程間通信

class Queue
{
public:
	void put(int num)
	{
		unique_lock<std::mutex> lck(mtx);
		while (!que.empty())
		{
			cv.wait(lck);
		}
		que.push(num);
		cv.notify_all();
		cout << "生產(chǎn)者,生產(chǎn)了:" << num << "號(hào)產(chǎn)品" << endl;
	}
	void get()
	{
		unique_lock<std::mutex> lck(mtx);
		while (que.empty())
		{
			cv.wait(lck);
		}
		int val = que.front();
		que.pop();
		cv.notify_all();
		cout << "消費(fèi)者,消費(fèi)了:" << val << "號(hào)產(chǎn)品" << endl;
	}
private:
	queue<int> que;
};

void producer(Queue* que)
{
	for (int i = 0; i < 10; ++i)
	{
		que->put(i);
		std::this_thread::sleep_for(std::chrono::milliseconds(200));
	}
}

void consumer(Queue* que)
{
	for (int i = 0; i < 10; ++i)
	{
		que->get();
		std::this_thread::sleep_for(std::chrono::milliseconds(200));
	}
}
int main()
{
	Queue que;

	std::thread t1(producer, &que);
	std::thread t2(consumer, &que);

	t1.join();
	t2.join();

	return 0;
}

這個(gè)時(shí)候我們?cè)賮?lái)看運(yùn)行結(jié)果:

在這里插入圖片描述

這個(gè)時(shí)候就是:
生產(chǎn)一個(gè)、消費(fèi)一個(gè)。

原子類(lèi)型(atomic)

我們前面遇到線(xiàn)程不安全的問(wèn)題,主要是因?yàn)樯婕?code>++、--操作的時(shí)候,有可能被其他的線(xiàn)程干擾,所以使用了互斥鎖。
只允許得到的線(xiàn)程進(jìn)行操作;
其他沒(méi)有得到的線(xiàn)程只能眼巴巴的干看著。
但是,對(duì)于互斥鎖來(lái)說(shuō),它是比較重的,它對(duì)于臨界區(qū)代碼做的事情比較復(fù)雜。
簡(jiǎn)單來(lái)說(shuō),如果只是為了++、--這樣的簡(jiǎn)單操作互斥的話(huà),使用互斥鎖,就有點(diǎn)殺雞用牛刀的意味了。
那么有沒(méi)有比互斥鎖更加輕量的,并且能夠解決問(wèn)題的呢?
當(dāng)然有,就是我們要說(shuō)的原子類(lèi)型。

簡(jiǎn)單使用

我們可以簡(jiǎn)單設(shè)置一個(gè)場(chǎng)景:
定義十個(gè)線(xiàn)程,對(duì)一個(gè)公有的變量myCount進(jìn)行task的操作,該操作是對(duì)變量進(jìn)行100次的++
所以,如果順利,我們會(huì)最終得到myCount = 1000。
代碼如下:

#include <iostream>
#include <thread>
#include <atomic>
#include <list>

volatile std::atomic_bool isReady = false;
volatile std::atomic_int myCount = 0;

void task()
{
	while (!isReady)
	{
		// 線(xiàn)程讓出當(dāng)前的CPU時(shí)間片,等待下一次調(diào)度
		std::this_thread::yield();
	}

	for (int i = 0; i < 100; ++i)
	{
		myCount++;
	}
}

int main()
{
	std::list<std::thread> tlist;
	for (int i = 0; i < 10; ++i)
	{
		tlist.push_back(std::thread(task));
	}

	std::this_thread::sleep_for(std::chrono::milliseconds(200));
	isReady = true;

	for (std::thread& it : tlist)
	{
		it.join();
	}
	std::cout << "myCount:" << myCount << std::endl;

	return 0;
}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

改良車(chē)站賣(mài)票

對(duì)于原子類(lèi)型來(lái)說(shuō),使用方法非常簡(jiǎn)單:
首先包含頭文件:#include <atomic>
接著把需要原子操作的變量定義為對(duì)應(yīng)的原子類(lèi)型就好:
bool -> atomic_bool;
int -> atomic_int;
其他同理。
理解了這個(gè)以后,我們可以使用原子類(lèi)型對(duì)我們的車(chē)站賣(mài)票進(jìn)行改良:

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <atomic>
using namespace std;

std::atomic_int tickets = 100; // 車(chē)站剩余票數(shù)總數(shù)

void sellTickets(int win)
{
	while (tickets > 0)
	{
		tickets--;
		cout << "窗口:" << win << " 賣(mài)出了第:" << tickets << "張票!" << endl;
	}
}

int main()
{
	list<std::thread> tlist;
	for (int i = 1; i <= 3; ++i)
	{
		tlist.push_back(std::thread(sellTickets, i));
	}
	for (std::thread& t : tlist)
	{
		t.join();
	}
	cout << "所有窗口賣(mài)票結(jié)束!" << endl;

	return 0;
}

可以看到,從代碼長(zhǎng)度來(lái)說(shuō)就輕量了很多!
運(yùn)行結(jié)果如下:

在這里插入圖片描述

雖然還有部分打印亂序的情況:
(畢竟線(xiàn)程的執(zhí)行順序誰(shuí)也摸不清 😦 )
但是,代碼的邏輯沒(méi)有問(wèn)題!
不會(huì)出現(xiàn)一張票被賣(mài)了多次的情況!
這個(gè)原子類(lèi)型也被叫做:無(wú)鎖類(lèi)型,像是一些無(wú)鎖隊(duì)列之類(lèi)的實(shí)現(xiàn),就是靠的這個(gè)東西。

以上就是C++線(xiàn)程間的互斥和通信的詳細(xì)內(nèi)容,更多關(guān)于C++線(xiàn)程間通信的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C及C++中typedef的簡(jiǎn)單使用介紹

    C及C++中typedef的簡(jiǎn)單使用介紹

    C/C++中關(guān)鍵字typedef的理解不是多透徹,今天小編抽空給大家分享下C及C++中typedef的簡(jiǎn)單使用介紹,需要的朋友可以參考下
    2016-10-10
  • 實(shí)現(xiàn)一個(gè)內(nèi)存池管理的類(lèi)方法

    實(shí)現(xiàn)一個(gè)內(nèi)存池管理的類(lèi)方法

    下面小編就為大家?guī)?lái)一篇實(shí)現(xiàn)一個(gè)內(nèi)存池管理的類(lèi)方法。小編覺(jué)得挺不錯(cuò)的現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-01-01
  • C++用mysql自帶的頭文件連接數(shù)據(jù)庫(kù)

    C++用mysql自帶的頭文件連接數(shù)據(jù)庫(kù)

    現(xiàn)在正做一個(gè)接口,通過(guò)不同的連接字符串操作不同的數(shù)據(jù)庫(kù)。要用到mysql數(shù)據(jù)庫(kù)。通過(guò)網(wǎng)上的一些資料和自己的摸索,大致清楚了C++連接mysql的方法??梢酝ㄟ^(guò)2種方法實(shí)現(xiàn)。第一種方法是利用ADO連接,第二種方法是利用mysql自己的api函數(shù)進(jìn)行連接。今天主要來(lái)講解下使用API
    2016-07-07
  • C++中atof?函數(shù)的介紹

    C++中atof?函數(shù)的介紹

    這篇文章主要給大家分享的是C++中atof?函數(shù)的介紹,在?stdlib.h?中?atof?函數(shù),可用于將?char?字符串轉(zhuǎn)為?float?/?double?浮點(diǎn)數(shù)類(lèi)型,想具體了解語(yǔ)法的小伙伴可以參考下面文章的內(nèi)容,希望對(duì)大家有所幫助
    2021-11-11
  • C++實(shí)現(xiàn)LeetCode(188.買(mǎi)賣(mài)股票的最佳時(shí)間之四)

    C++實(shí)現(xiàn)LeetCode(188.買(mǎi)賣(mài)股票的最佳時(shí)間之四)

    這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(188.買(mǎi)賣(mài)股票的最佳時(shí)間之四),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • OpenCV利用高斯模糊實(shí)現(xiàn)簡(jiǎn)單的磨皮美顏效果

    OpenCV利用高斯模糊實(shí)現(xiàn)簡(jiǎn)單的磨皮美顏效果

    這篇文章主要介紹了通過(guò)OpenCV中的高斯模糊以及雙邊模糊來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的磨皮美顏效果,文中的講解很詳細(xì),感興趣的同學(xué)可以學(xué)習(xí)一下
    2021-12-12
  • C語(yǔ)言 二級(jí)指針詳解及示例代碼

    C語(yǔ)言 二級(jí)指針詳解及示例代碼

    本文主要介紹C語(yǔ)言 二級(jí)指針,這里整理了C語(yǔ)言中二級(jí)指針的基礎(chǔ)資料并附有示例代碼和實(shí)現(xiàn)結(jié)果,幫助大家學(xué)習(xí)理解相關(guān)知識(shí),有學(xué)習(xí)的朋友可以參考下
    2016-08-08
  • c病毒程序原理分析(防范病毒 c語(yǔ)言小病毒示例)

    c病毒程序原理分析(防范病毒 c語(yǔ)言小病毒示例)

    這篇文章主要介紹了病毒程序原理,寫(xiě)個(gè)小程序做演示,大家可以參考這個(gè)以防中相似C病毒
    2013-12-12
  • 基于C語(yǔ)言實(shí)現(xiàn)的貪吃蛇游戲完整實(shí)例代碼

    基于C語(yǔ)言實(shí)現(xiàn)的貪吃蛇游戲完整實(shí)例代碼

    這篇文章主要介紹了基于C語(yǔ)言實(shí)現(xiàn)的貪吃蛇游戲完整實(shí)例代碼,對(duì)于學(xué)習(xí)游戲開(kāi)發(fā)的朋友有一定的借鑒價(jià)值,需要的朋友可以參考下
    2014-08-08
  • visual studio 2015下boost庫(kù)配置教程

    visual studio 2015下boost庫(kù)配置教程

    這篇文章主要為大家詳細(xì)介紹了visual studio 2015下boost庫(kù)的配置教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-04-04

最新評(píng)論