C++学习笔记总结练习:并发控制-线程同步与数据访问-mutex和lock

4 线程同步与数据访问

存在问题

多个线程共享资源出现访问冲突

  • 读读互补冲突
  • 读写冲突
  • 写写冲突

解决问题的方法

保证操作的原子性和次序。atomicity不可分割。order按次序执行。

  • future和promise能够保证原子性和次序。一定是在形成返回值和异常后,future才会读取数据,否则进行堵塞。
  • mutex和lock
  • condition variable
  • atomic data type底层接口

4.1 mutex和lock

mutex简单说明mutex

函数 作用
lock 锁定互斥,若互斥不可用则阻塞
try_lock 尝试锁定互斥,若互斥不可用则返回
unlock 解锁互斥
int val ;
mutex valMutex;
valMutex.lock();

//val的访问和修改

valMutex.unlock();
  • 每次访问前上锁。访问后开锁。
  • 如果其他程序已经上锁,那么当前程序阻塞,直到其他程序释放锁。(发送开锁信号激活)
  • 存在的问题:中途出现异常,无法执行开锁。资源会被永久上锁。
  • mutex尝试锁try_lock()用来判断资源是否上锁。如果成功就返回true,此时调用可以上锁
mutex m;

while(m.try_lock()==false){
    doSomethingOthers();
}

lock_guard lg(m,adopt_lock);

mutex递归锁recursive_mutex

  • recursive_mutex与mutex操作完全一致。
  • 死锁:两个程序分别锁上了对方需要的资源,并在相互等待。
  • 递归锁:一个线程两次上锁,导致第二次上锁的时候资源被自己占用。也是一种死锁。
  • recursive_mutex 能够防止递归锁出现。即防止同一个线程多次上锁同一个资源

mutex时间锁timed_mutex/recursive_time_mutex

函数 作用
lock 锁定互斥,若互斥不可用则阻塞
try_lock 尝试锁定互斥,若互斥不可用则返回
try_lock_for 尝试锁定互斥,若互斥在指定的时限时期中不可用则返回
try_lock_until 尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回
(公开成员函数)
unlock 解锁互斥
  • 等待某个时间段。返回是否上锁。有如下成员函数
try_lock_for()
try_lock_until()

mutex进阶版本lock_guard

std::lock_guard sbguard1(my_mutex1, std::adopt_lock);// std::adopt_lock标记作用;
  • 使用lock_guard管理锁。这样当出现异常后,lock_guard局部变量被销毁,执行析构函数的时候回自动释放资源锁。
  • lock_guard的第二个标质量adopt_lock标记的效果就是假设调用一方已经拥有了互斥量的所有权(已经lock成功了);通知lock_guard不需要再构造函数中lock这个互斥量了。
//mute & lock
#include
#include
#include
#include

using namespace std;

//互斥体的控制变量
mutex printMutex;

void print(const std::string&s){
    // 如果没有枷锁,多个线程共同调用会乱序输出
    lock_guard l(printMutex);
    for(char c:s){
        cout.put(c);
    }
    cout<

mutex包装器unique_lock

  • 加强版的lock_guard。保存一个mutex对象。可以只是用这一个函数解决所有问题。
  • lock_gurad只能在析构函数中解锁,无法在同一个线程中进行细粒度的控制。但是unique_lock可以自己加锁解锁。
函数 作用
lock 锁定关联互斥
try_lock 尝试锁定关联互斥,若互斥不可用则返回
try_lock_for 试图锁定关联的可定时锁定 (TimedLockable) 互斥,若互斥在给定时长中不可用则返回
try_lock_until 尝试锁定关联可定时锁定 (TimedLockable) 互斥,若抵达指定时间点互斥仍不可用则返回
unlock 解锁关联互斥
release 将关联互斥解关联而不解锁它.返回unique_lock所有的锁的指针。可以自己解锁
mutex 返回指向关联互斥的指针
owns_lock 测试锁是否占有其关联互斥
operator bool 测试锁是否占有其关联互斥

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cvrsbEOC-1691759243705)(image/2021-03-08-18-31-27.png)]

void shared_print(string msg, int id) {

        std::unique_lock guard(_mu);
        //do something 1
        guard.unlock(); //临时解锁

        //do something 2

        guard.lock(); //继续上锁
        // do something 3
        f << msg << id << endl;
        cout << msg << id << endl;
        // 结束时析构guard会临时解锁
        // 这句话可要可不要,不写,析构的时候也会自动执行
        // guard.ulock();
    }

4.2 condition variable

简介

  • future的目的是处理线程的返回值和异常。因为它只能携带一次数据返回。
  • 这个明显是解决生产者和消费者问题。或者读、写问题。因为资源有数量限制。而之前的mutex只有互斥限制,也就是说,mutex与lock只能控制数量为1的消费者互斥访问问题。
  • condition variable控制数量大于1 的生产和消费问题

condition_variable原理

  • condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。

  • 有意修改变量的线程必须

    1. 获得 std::mutex (常通过 std::lock_guard )
    2. 在保有锁时进行修改
    3. 在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁)
  • 即使共享变量是原子的,也必须在互斥下修改它,以正确地发布修改到等待的线程。

  • 任何有意在 std::condition_variable 上等待的线程必须

    1. 在与用于保护共享变量者相同的互斥上获得 std::unique_lockstd::mutex
    2. 执行下列之一:
      1. 检查条件,是否为已更新或提醒它的情况
      2. 执行 wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行。
      3. condition_variable 被通知时,时限消失或虚假唤醒发生,线程被唤醒,且自动重获得互斥。之后线程应检查条件,若唤醒是虚假的,则继续等待。
      4. 或者,使用 wait 、 wait_for 及 wait_until 的有谓词重载,它们包揽以上三个步骤

condition_variable的消费者。有一下三种情况。

  • 当资源没有被生产出来,没有加锁时,加锁,wait(),解锁,等待通知。
  • 当资源被锁时,在unique_lock处等待解锁。
  • 当资源生产出来,没有加锁时,直接执行。

对于第一种情况:condition_varaiblewait操作能够解锁等待信号量。当信号量来到时,加锁执行操作,然后解锁,退出。当信号量来到时,加锁,但是第二个参数的内容发现是虚假信号,能够继续解锁等待信号量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TDT4830s-1691759243706)(image/2021-03-08-19-36-00.png)]

condition_variable操作

函数 作用
notify_one 通知一个等待的线程
notify_all 通知所有等待的线程
wait 阻塞当前线程,直到条件变量被唤醒
wait_for 阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后
wait_until 阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点

使用条件

  • 一个“存放数据”的对象,或一个“表示条件满足”的flag。此处的readyFlag
  • 一个mutex对象,此处的readyMutex
  • 一个condition_variable对象词的readyCondVar

编程实现——简单使用

//condition variable生产者消费者问题
#include
#include
#include
#include

using namespace std;


bool readyFlag;
mutex readyMutex;
condition_variable readyCondVar;

void thread1(){
    cout<<"thread1"< lg(readyMutex);
        readyFlag = true;
    }
    readyCondVar.notify_one();
}

void thread2(){
    {
        unique_lock ul(readyMutex);
        readyCondVar.wait(ul,[]{return readyFlag;});
    }

    cout<<"done"<

编程实现——多线程Queue

  • 生产者和消费者问题
//condition variable实现多线程queue
#include
#include
#include
#include
#include
#include

using namespace std;

queue que;//消费对象
mutex queueMutex;
condition_variable queueCondVar;

//生产者
void provider(int val){
    for(int i=0;i<6;++i){
        lock_guard lg(queueMutex);
        que.push(val+i);
        //貌似这句话会被优化掉
        this_thread::sleep_for(chrono::microseconds(100000));
    }
    queueCondVar.notify_one();

    this_thread::sleep_for(chrono::microseconds(val));
}


//消费者
void consumer(int num){
    while(true){
        int val;
        {
            unique_lock ul(queueMutex);
            queueCondVar.wait(ul,[]{return !que.empty();});
            val = que.front();
            que.pop();
            cout<<"consumer"<

4.3 atomic data

等到以后再写吧。感觉没有必要。

5 this_thread

函数 作用
get_id 获得thread id (function )
yield 放弃执行 (function )
sleep_until 休眠到某个时间节点chrono::timepoint (function )
sleep_for 休眠某个时间段chrono::duration (function )

你可能感兴趣的:(c++,c语言,Linux开发,c++,学习,笔记)