激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - C++多線程之互斥鎖與死鎖

C++多線程之互斥鎖與死鎖

2022-03-11 13:54HerofH_ C/C++

互斥鎖和死鎖是C++多線程中常見的情況,這篇文章就帶大家進一步了解多線程中的互斥鎖與死鎖這兩個概念,文中的示例代碼介紹得很詳細,快來跟隨小編一起學習吧

1.前言

比如說我們現在以一個list容器來模仿一個消息隊列,當消息來臨時插入list的尾部,當讀取消息時就把頭部的消息讀出來并且刪除這條消息。在代碼中就以兩個線程分別實現消息寫入和消息讀取的功能,如下:

class msgList
{
private:
	list<int>mylist;   //用list模仿一個消息隊列

public:
	void WriteList()   //向消息隊列中寫入消息(以i作為消息)
	{
		for (int i = 0; i<100000; i++)
		{
			cout << "Write : " << i <<endl;
			mylist.push_back(i);
		}
		return;
	}
	void ReadList()  //從消息隊列中讀取并取出消息
	{
		for(int i=0;i<100000;i++)
		{
			if (!mylist.empty())
			{
				cout << "Read : " << mylist.front() << endl;
				mylist.pop_front();
			}
			else
			{
				cout << "Message List is empty!" << endl;
			}
		}
	}
};
int main()
{
	msgList mlist;
	thread pread(&msgList::ReadList, &mlist);   //讀線程
	thread pwrite(&msgList::WriteList, &mlist);   //寫線程
   //等待線程結束
	pread.join(); 
	pwrite.join();

  return 0;
}

這段程序在運行過程中,大部分時間是正常的,但是也會出現如下不穩定的情況:

C++多線程之互斥鎖與死鎖

為什么會出現這種情況呢?

這是因為消息隊列對于讀線程和寫線程來說是共享的,這時就會出現兩種特殊的情況:讀線程的讀取操作還沒有結束,線程上下文就切換到了寫線程中;或者寫線程的寫入操作還沒有結束,線程上下文切換就到了讀線程中,這兩種情況都反映了讀寫沖突,從而出現了以上錯誤。

要想解決這個問題,最顯然最直接的方法就是將讀寫操作分離開來,讀的時候不允許寫,寫的時候不允許讀,這樣,才能實現線程安全的讀和寫。說形象一點,就是在進行讀操作時,就對共享資源進行加鎖,禁止其他線程訪問,其他線程要訪問就得等到讀線程解鎖才行,就像上廁所一樣,一次只能上一個人,其他人必須得等他上完了再上。這樣,就有了互斥鎖的概念。

 

2.互斥鎖

在多任務操作系統中,同時運行的多個任務可能都需要使用同一種資源。比如說,同一個文件,可能一個線程會對其進行寫操作,而另一個線程需要對這個文件進行讀操作,可想而知,如果寫線程還沒有寫結束,而此時讀線程開始了,或者讀線程還沒有讀結束而寫線程開始了,那么最終的結果顯然會是混亂的。為了保護共享資源,在線程里也有這么一把鎖——互斥鎖(mutex),互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即上鎖( lock )和解鎖( unlock )。

2.1 互斥鎖的特點

1. 原子性:把一個互斥量鎖定為一個原子操作,這意味著如果一個線程鎖定了一個互斥量,沒有其他線程在同一時間可以成功鎖定這個互斥量;

2. 唯一性:如果一個線程鎖定了一個互斥量,在它解除鎖定之前,沒有其他線程可以鎖定這個互斥量;

3. 非繁忙等待:如果一個線程已經鎖定了一個互斥量,第二個線程又試圖去鎖定這個互斥量,則第二個線程將被掛起(不占用任何cpu資源),直到第一個線程解除對這個互斥量的鎖定為止,第二個線程則被喚醒并繼續執行,同時鎖定這個互斥量。

2.2 互斥鎖的使用

根據前面我們可以知道,互斥鎖主要就是用來保護共享資源的,在C++ 11中,互斥鎖封裝在mutex類中,通過調用類成員函數lock()和unlock()來實現加鎖和解鎖。值得注意的是,加鎖和解鎖,必須成對使用,這也是比較好理解的。除此之外,互斥量的使用時機,就以開篇程序為例,我們要保護的共享資源當然就是消息隊列list了,那么互斥鎖應該加在哪里呢?

可能想的比較簡單一點:就直接把鎖加在函數最前面不就好了么?如下所示:

class msgList
{
private:
	list<int>mylist;   //用list模仿一個消息隊列
      mutex mtx;   //創建互斥鎖對象
public:
	void WriteList()   //向消息隊列中寫入消息(以i作為消息)
	{
              mtx.lock();
		for (int i = 0; i<100000; i++)
		{
			cout << "Write : " << i <<endl;
			mylist.push_back(i);
		}
              mtx.unlock();
		return;
	}
	//.......
};

不過如果這樣加鎖的話,要等寫線程完全執行結束才能開始讀線程,讀寫線程變成了串行執行,這就違背了線程并發性的特點了。正確的加鎖方式應當是在執行寫操作的具體部分加鎖,如下所示:

class msgList
{
private:
	list<int>mylist;   //用list模仿一個消息隊列
      mutex mtx;   //創建互斥鎖對象
public:
	void WriteList()   //向消息隊列中寫入消息(以i作為消息)
	{   
		for (int i = 0; i<100000; i++)
		{
                      mtx.lock();
			cout << "Write : " << i <<endl;
			mylist.push_back(i);
                      mtx.unlock();
		}
		return;
	}
	//.......
};

這樣,才能真正的實現讀寫互不干擾。

下面再舉一個更為直觀的例子,創建兩個線程同時對list進行寫操作:

class msgList
{
private:
	list<int>mylist;
	mutex m;
	int i = 0;
public:
	void WriteList()
	{
		while(i<1000)
		{
			mylist.push_back(i++);
		}
		return;
	}
	void showList()
	{
		for (auto p = mylist.begin(); p != mylist.end(); p++)   
		{
			cout << (*p) << " ";
		}
		cout << endl;
		cout << "size of list : " << mylist.size() << endl;
		return;
	}
};
int main()
{
	msgList mlist;
	thread pwrite0(&msgList::WriteList, &mlist);
	thread pwrite1(&msgList::WriteList, &mlist);

	pwrite0.join();
	pwrite1.join();
	cout << "threads end!" << endl;

	mlist.showList();  //子線程結束后主線程打印list
  return 0;
}

這里用兩個線程來寫list,并且最終在主線程中調用了showList()來輸出list的size和所有元素,我們先來看下輸出情況:

C++多線程之互斥鎖與死鎖

根據結果可以看到,這里有很多問題:實際輸出的元素個數和size不符,輸出的元素也并不是連續的,這都是多個線程同時更新list所造成的情況。這種情況下,運行結果是無法預料的,每次都可能不一樣。這就是線程不安全所引發的問題,我們加上鎖再來看看:

class msgList
{
private:
	list<int>mylist;
	mutex m;
	int i = 0;
public:
	void WriteList()
	{
		while(i<1000)
		{
                      m.lock();//加鎖
			mylist.push_back(i++);
                      m.unlock(); //解鎖
		}
		return;
	}
	// ......
};

C++多線程之互斥鎖與死鎖

這樣加鎖就正確了嗎?我們再多運行幾次看看:

C++多線程之互斥鎖與死鎖

數字都是連續的,但是個數卻多了一個(出現的幾率還是比較?。?,這又是什么原因造成的呢?還是兩個線程的問題,假設要插入1000個數,循環條件就是while(i<1000),當i=999的時候兩個寫線程都可以進入while循環,此時如果pwrite0線程拿到了lock(),那么pwrite1線程就只能一直等待,pwrite0線程繼續往下執行,使得i變成了1000,此時,對于pwrite0線程來說,它就必須退出循環了。而此時的pwrite1在哪里呢?還等在lock()的地方,pwrite0線程unlock()后,pwrite1成功lock(),此時i=1000,但是pwrite1卻還沒有執行完此次循環,因此向list中插入1000,此時退出的i的值為1001,這也就造成了實際輸出為1001個數的情況。

為了避免這個問題,一個簡單的辦法就是在lock()之后再加上一個判斷,判斷i是否依舊滿足while的條件,如下:

void WriteList()
	{
		while(i<10000)
		{
			m.lock();
			if (i >= 10000)
			{
				m.unlock();   //退出之前必須先解鎖
				break;
			}
			mylist.push_back(i++);
			m.unlock();
		}
		return;
	}

為什么這里要在break前面加一個unlock()呢?原因就在于:如果break前面沒有unlock(),一旦i符合了if的條件,就直接break了,此時就沒法unlock(),程序就會報錯:

C++多線程之互斥鎖與死鎖

可以發現,這種錯誤是比較難發現的,特別是像這樣程序中出現了分支的情況,很容易就使得程序實際運行時lock()了卻沒有unclock()。為了解決這一問題,就有了std::lock_guard。

2.3 std::lock_guard

簡單來理解的話,lock_guard就是一個類,它會在其構造函數中加鎖,而在析構函數中解鎖,也就是說,只要創建一個lock_guard的對象,就相當于lock()了,而該對象析構時,就自動調用unlock()了。

就以上述程序為例,直接改寫為:

void WriteList()
	{
		while(i<10000)
		{
                      lock_guard<mutex> guard(m);  //創建lock_guard的類對象guard,用互斥量m來構造
			//m.lock();   
			if (i >= 10000)
			{
				//m.unlock();   //由于有了guard,這里就無需unlock()了
				break;
			}
			mylist.push_back(i++);
			//m.unlock();
		}
		return;
	}

這里主要有兩個需要注意的地方:第一、原先的lock()和unlock()都不用了;第二、if中的break前面也不用再調用unlock()了。這都是因為對象guard在lock_guard一句處構造出來,同時就調用了lock(),當退出while時,guard析構,析構時就調用了unlock()。(局部對象的生命周期就是創建該對象時離其最近的大括號的范圍{})

 

3.死鎖

3.1 死鎖的含義

死鎖是什么意思呢?舉個例子,我和你手里都拽著對方家門的鑰匙,我說:“你不把我的鎖還來,我就不把你的鎖給你!”,你一聽不樂意了,也說:“你不把我的鎖還來,我也不把你的鎖給你!”就這樣,我們兩個人互相拿著對方的鎖又等著對方先把鎖拿來,然后就只能一直等著等著等著......最終誰也拿不到自己的鎖,這就是死鎖。

顯然,死鎖是發生在至少兩個鎖之間的,也就是指由于兩個或者多個線程互相持有對方所需要的資源,導致這些線程處于等待狀態,無法前往執行,當線程互相持有對方所需要的資源時,會互相等待對方釋放資源,如果線程都不主動釋放所占有的資源,將產生死鎖。

3.2 死鎖的例子

mutex m0,m1;
int i = 0;
void fun0()
{
	while (i < 100)
	{
		lock_guard<mutex> g0(m0);  //線程0加鎖0
		lock_guard<mutex> g1(m1);  //線程0加鎖1
		cout << "thread 0 running..." << endl;
	}
	return;
}
void fun1()
{
	while (i < 100)
	{
		lock_guard<mutex> g1(m1);  //線程1加鎖1
		lock_guard<mutex> g0(m0);  //線程1加鎖0
		cout << "thread 1 running...   "<< i << endl;
	}
	return;
}
int main()
{
	thread p0(fun0);
	thread p1(fun1);
	p0.join();
	p1.join();
  return 0;
}

我們來看下運行結果:

C++多線程之互斥鎖與死鎖

這就出現了死鎖。產生的原因就是因為在線程0中,先加鎖0,再加鎖1;在線程1中,先加鎖1,再加鎖0;如果兩個線程之一能夠完整執行的話,那自然是沒有問題的,但是如果某個時刻,線程0中剛加鎖0,就上下文切換到線程1,此時線程1就加鎖1,然后此時兩個線程都想向下執行的話,線程1就必須等待線程0解鎖0,線程0就必須等待線程1解鎖1,就這樣兩個線程都一直阻塞著,形成了死鎖。

3.3 死鎖的解決方法

①按順序加鎖

以上述例程來說,就是線程0和線程1的加鎖順序保持一致,如下所示:

mutex m0,m1;
int i = 0;
void fun0()
{
	while (i < 100)
	{
		lock_guard<mutex> g0(m0);  //線程0加鎖0
		lock_guard<mutex> g1(m1);  //線程0加鎖1
		cout << "thread 0 running..." << endl;
	}
	return;
}
void fun1()
{
	while (i < 100)
	{
              lock_guard<mutex> g0(m0);  //線程1加鎖0
		lock_guard<mutex> g1(m1);  //線程1加鎖1
		cout << "thread 1 running...   "<< i << endl;
	}
	return;
}
int main()
{
	thread p0(fun0);
	thread p1(fun1);
	p0.join();
	p1.join();
  return 0;
}

C++多線程之互斥鎖與死鎖

在這種情況下,兩個線程一旦一個加了鎖,那么另一個就必定阻塞,這樣,就不會出現兩邊加鎖兩邊阻塞的情況,從而避免死鎖。

②同時上鎖

同時上鎖需要用到lock()函數,如下所述:

mutex m0,m1;
int i = 0;
void fun0()
{
	while (i < 100)
	{
              lock(m0,m1);
		lock_guard<mutex> g0(m0, adopt_lock);
		lock_guard<mutex> g1(m1, adopt_lock);
		cout << "thread 0 running..." << endl;
	}
	return;
}
void fun1()
{
	while (i < 100)
	{
              lock(m0,m1);
		lock_guard<mutex> g0(m0, adopt_lock);
		lock_guard<mutex> g1(m1, adopt_lock);
		cout << "thread 1 running...   "<< i << endl;
	}
	return;
}
int main()
{
	thread p0(fun0);
	thread p1(fun1);
	p0.join();
	p1.join();
  return 0;
}

C++多線程之互斥鎖與死鎖

注意到這里的lock_guard中多了第二個參數adopt_lock,這個參數表示在調用lock_guard時,已經加鎖了,防止lock_guard在對象生成時構造函數再次lock()。 

以上就是C++多線程之互斥鎖與死鎖的詳細內容,更多關于C++ 多線程 互斥鎖 死鎖的資料請關注服務器之家其它相關文章!

原文鏈接:https://blog.csdn.net/qq_28114615/article/details/88367016

延伸 · 閱讀

精彩推薦
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
主站蜘蛛池模板: 韩国草草影院 | 影视免费观看 | asian gaysex| 性少妇videosexfreexx | 欧美人xx | 久久久www免费看片 亚洲综合视频一区 | 色阁阁69婷婷 | 国产成人av在线播放 | www.三区| 久久久aa | 欧美国产日韩在线观看成人 | 黄色一级片免费观看 | 免费三级大片 | 麻豆小视频在线观看 | 免费黄色小视频网站 | 精品一区二区久久久久久久网精 | 亚洲小视频网站 | 日本在线播放一区二区 | 成人免费av在线 | 高清国产午夜精品久久久久久 | 国产精品一品二区三区四区18 | 久久av免费 | 精品国产一区二区久久 | 日本在线不卡一区二区三区 | 欧美激情精品久久久久久久久久 | 日本在线不卡一区二区三区 | 国产亚洲精品成人 | 国产69精品久久久久9999不卡免费 | 黄色影院一级片 | 国产一区二区三区四区在线 | 影视免费观看 | 午夜精品成人 | 久久99国产综合精品 | 国产一区二区亚洲 | 欧美日本91精品久久久久 | 成人毛片100免费观看 | 国产美女爽到喷白浆的 | 91不卡在线 | 精品一区二区三区中文字幕老牛 | 久久精品电影网 | 欧美视频国产精品 |