这篇“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; }
最终,实现了两个线程交替打印(一个线程打印奇数、一个线程打印偶数)