C++詳解如何實現兩個線程交替打印

C++線程庫,點擊此處查看文檔

首先簡單搭一個框架,讓兩個線程先嘗試實現交替打印。

//實現兩個線程交替打印
#include <iostream>
#include <thread>
using namespace std;
int main(void)
{
	int n = 100;
	int i = 0;
	//創建兩個線程
	thread t1([&n, &i](){
		while (i < n)
		{
			cout << i << " ";
			i++;
		}
	});
	thread t2([&n, &i]() {
		while (i < n)
		{
			cout << i << " ";
			i++;
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

為瞭讓我們更加清楚是哪個線程打印瞭,我們需要獲取線程的ID。

#include <iostream>
#include <thread>
using namespace std;
int main(void)
{
	int n = 100;
	int i = 0;
	//創建兩個線程
	thread t1([&n, &i](){
		while (i < n)
		{
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
		}
	});
	thread t2([&n, &i]() {
		while (i < n)
		{
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

這顯然沒有完成兩個線程交替打印的目的,甚至數據的打印都非常地亂。這是因為i是臨界資源,多個線程爭搶訪問臨界資源可能會造成數據二義,線程是不安全的,需要保證任意時刻隻有一個線程能夠訪問臨界資源。

所以創建一個互斥量,並在臨界區合適的地方加鎖和解鎖。由於線程的執行函數我使用瞭lambda表達式,為瞭讓兩個線程使用的是同一把鎖,把鎖創建在瞭main函數內,並在lambda表達式內使用瞭引用捕捉。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	//創建兩個線程
	thread t1([&n, &i, &mtx](){
		while (i < n)
		{
			mtx.lock();
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
			mtx.unlock();
		}
	});
	thread t2([&n, &i, &mtx]() {
		while (i < n)
		{
			mtx.lock();
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
			mtx.unlock();
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

在C++中,一般不直接操作鎖,而是由類去管理鎖。

//第一個管理鎖的類
template <class Mutex> class lock_guard;
//第二個管理鎖的類
template <class Mutex> class unique_lock;

lock_guar類,隻有構造和析構函數。一般用於加鎖和解鎖,這裡進行簡單的模擬:

//註意:為瞭使得加鎖和解鎖的是同一把鎖
//需要使用引用
template <class Lock>
class LockGuard
{
public:
	LockGuard(Lock &lck)
		:_lock(lck)
	{
		_lock.lock();
	}
	~LockGuard()
	{
		_lock.unlock();
	}
private:
	Lock &_lock;
};

unique_lock的成員方法就不僅僅是析構函數和構造函數。詳見文檔unique_lock介紹和使用。

這裡將鎖交給unique_lock類的對象進行管理。

int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	//創建兩個線程
	thread t1([&n, &i, &mtx, &cv, &flag](){
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
		}
	});
	thread t2([&n, &i, &mtx, &cv, &flag]() {
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

線程是安全瞭,但如果其中一個線程競爭鎖的能力比較強,那麼可能會出現上面這種情況。

需要控制:一個線程執行一次後,如果再次去執行就不準許瞭,同時可以喚醒另一個進程去執行,如此循環往復達到交替打印的目的。所以可以增加一個條件變量,讓某個線程在該條件變量下的阻塞隊列等待。

C++庫中線程在條件變量下的等待函數第一個參數註意是管理鎖的類對象

int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	condition_variable cv;
	bool flag = false;
	//創建兩個線程
	thread t1([&n, &i, &mtx, &cv, &flag](){
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			//!flag為真,那麼獲取後不會阻塞,優先運行
			cv.wait(LockManage, [&flag]() {return !flag; });
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
		}
	});
	thread t2([&n, &i, &mtx, &cv, &flag]() {
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			//flag為假,競爭到鎖後,由於條件不滿足,阻塞
			cv.wait(LockManage, [&flag]() {return flag; });
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

這裡flag以及lambda表達式的增加是非常巧妙的。flag的初始化值為false,讓線程t2在[&flag]() {return false; }下等待,那麼t2線程就會先執行。

線程t1競爭到瞭鎖,但是由於不滿足條件,會繼續等待,所以就出現瞭上面的情況。

需要一個線程喚醒另一個線程之前,將flag的值進行修改。

int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	condition_variable cv;
	bool flag = false;
	//創建兩個線程
	thread t1([&n, &i, &mtx, &cv, &flag](){
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			//!flag為真,那麼獲取後不會阻塞,優先運行
			cv.wait(LockManage, [&flag]() {return !flag; });
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
			flag = true;
			cv.notify_one();
		}
	});
	thread t2([&n, &i, &mtx, &cv, &flag]() {
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			//flag為假,競爭到鎖後,由於條件不滿足,阻塞
			cv.wait(LockManage, [&flag]() {return flag; });
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
			flag = false;
			cv.notify_one();
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

最終,實現瞭兩個線程交替打印(一個線程打印奇數、一個線程打印偶數)

到此這篇關於C++詳解如何實現兩個線程交替打印的文章就介紹到這瞭,更多相關C++線程交替打印內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: