• 使用C++库实现两个线程交替打印(一个线程打印奇数、一个线程打印偶数)


    👉C++线程库,点击此处查看文档

    首先简单搭一个框架,让两个线程先尝试实现交替打印。

    //实现两个线程交替打印
    #include 
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    为了让我们更加清楚是哪个线程打印了,我们需要获取线程的ID。

    #include 
    #include 
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    d024b1c8875.png)

    这显然没有完成两个线程交替打印的目的,甚至数据的打印都非常地乱。这是因为i是临界资源,多个线程争抢访问临界资源可能会造成数据二义,线程是不安全的,需要保证任意时刻只有一个线程能够访问临界资源。

    所以创建一个互斥量,并在临界区合适的地方加锁和解锁。由于线程的执行函数我使用了lambda表达式,为了让两个线程使用的是同一把锁,把锁创建在了main函数内,并在lambda表达式内使用了引用捕捉。

    #include 
    #include 
    #include 
    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;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

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

    //第一个管理锁的类
    template <class Mutex> class lock_guard;
    //第二个管理锁的类
    template <class Mutex> class unique_lock;
    
    • 1
    • 2
    • 3
    • 4

    lock_guar类,只有构造和析构函数。一般用于加锁和解锁,这里进行简单的模拟:

    //注意:为了使得加锁和解锁的是同一把锁
    //需要使用引用
    template <class Lock>
    class LockGuard
    {
    public:
    	LockGuard(Lock &lck)
    		:_lock(lck)
    	{
    		_lock.lock();
    	}
    	~LockGuard()
    	{
    		_lock.unlock();
    	}
    private:
    	Lock &_lock;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述
    线程是安全了,但如果其中一个线程竞争锁的能力比较强,那么可能会出现上面这种情况。
    需要控制:一个线程执行一次后,如果再次去执行就不准许了,同时可以唤醒另一个进程去执行,如此循环往复达到交替打印的目的。所以可以增加一个条件变量,让某个线程在该条件变量下的阻塞队列等待。

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    这里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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    最终,实现了两个线程交替打印(一个线程打印奇数、一个线程打印偶数)
    在这里插入图片描述

  • 相关阅读:
    DAY26:GetShell专题
    关于ORVIBO的HomeAI V4.0的分析
    js左右联动实现滚动方法
    第1章:React 入门
    java datetime数据类型去掉时分秒
    leetcode 136. 只出现一次的数字
    2023系统架构设计师备考攻略!附完整备考资料!
    2023年中国人防服务需求现状及行业市场规模前景分析[图]
    【示波器专题】数字示波器的主要指标——采样率
    maven项目依赖报红解决办法
  • 原文地址:https://blog.csdn.net/qq_56870066/article/details/126493227