两个线程交替打印一个共享变量

首先给出基本框架

#include <iostream>
#include <thread>

using namespace std;

int main(){
    int n = 100;
    int i = 0;
    //创建两个线程
    thread newThread1([&n, &i](){
        while(i < n){
            cout << this_thread::get_id() << ": " << i << endl;
            i++;
        }    
    });
    thread newThread2([&n, &i](){
        while(i < n){
            cout << this_thread::get_id() << ": "  << i << endl;
            i++;
        }    
    });
    
    if(newThread1.joinable()){
        newThread1.join();
    }
    if(newThread2.joinable()){
        newThread2.join();
    }
    
    return 0;
}


这显然没有完成两个线程交替打印的目的,因为共享变量i是临界资源,所以会造成线程争抢,线程不安全,解决方法也很简单,创建一个互斥量mutex并在临界区合适的地方加锁和解锁。由于线程执行函数使用了lambda表达式,为了让两个线程使用同一把锁,把锁创建在了main函数内,并在lambda表达式内使用了引用捕捉。

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

int main(){
    int n = 100;
    int i = 0;
    mutex mtx;
    //创建两个线程
    thread newThread1([&n, &i, &mtx](){
        while(i < n){
            mtx.lock();
            cout << this_thread::get_id() << ": " << i << endl;
            i++;
            mtx.unlock();
        }    
    });
    thread newThread2([&n, &i, &mtx](){
        while(i < n){
            mtx.lock();
            cout << this_thread::get_id() << ": "  << i << endl;
            i++;
            mtx.unlock();
        }    
    });
    
    if(newThread1.joinable()){
        newThread1.join();
    }
    if(newThread2.joinable()){
        newThread2.join();
    }
    
    return 0;
}

但是在C++中,一般不操作锁,而是由类去管理,管理锁的类一般分为:

  • template <class Mutex> class lock_guard;
  • template <class Mutex> class unique_lock;
    其中第一种只有构造和析构函数,是锁的RAII简单实现。
    第二种是通用互斥锁包装器,支持延迟锁定,锁定的有时限尝试,递归锁定,所有权转移和与条件变量一起使用
    unique_lock比lock_guard更加灵活,功能更加强大;带来的缺点就是付出更多的时间和性能成本。
    这里使用第二种以便于之后与条件变量一起使用:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int main(){
    int n = 100;
    int i = 0;
    mutex mtx;
    //创建两个线程
    thread newThread1([&n, &i, &mtx](){
        while(i < n){
            unique_lock<mutex> LockManage(mtx);
            cout << this_thread::get_id() << ": " << i << endl;
            i++;
        }    
    });
    thread newThread2([&n, &i, &mtx](){
        while(i < n){
            unique_lock<mutex> LockManage(mtx);
            cout << this_thread::get_id() << ": "  << i << endl;
            i++;
        }    
    });
    
    if(newThread1.joinable()){
        newThread1.join();
    }
    if(newThread2.joinable()){
        newThread2.join();
    }
    
    return 0;
}

此时线程安全,但是若其中一个线程竞争锁的能力比较强,就会出现上述情况,大多数打印操作均由一个线程完成。
添加控制:一个线程执行一次后,再去执行就不被允许了,同时唤醒另外一个线程去执行。解决方案是添加一个条件变量,让某个线程在该条件变量下的阻塞队列等待。

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

int main(){
    int n = 100;
    int i = 0;
    mutex mtx;
    condition_variable cv;
    bool flag = false;
    //创建两个线程
    thread newThread1([&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();
            //调用notify_one()随机唤醒一个阻塞的线程,而其余线程仍处于阻塞状态,等待下一次唤醒。
        }    
    });
    thread newThread2([&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(newThread1.joinable()){
        newThread1.join();
    }
    if(newThread2.joinable()){
        newThread2.join();
    }
    
    return 0;
}

最终实现了两进程交替打印奇偶数。

posted @ 2023-03-01 16:28  yytarget  阅读(52)  评论(0编辑  收藏  举报