zoukankan      html  css  js  c++  java
  • 并发编程(3)线程间共享数据

    一、共享内存带来的问题

         读时没问题,写时会有竞争问题。

    二、解决方法

          1、最简单的办法就是对数据结构采用某种保护机制,确保只有进行修改的线程才能看到不变量被破坏时的中间状态。从其他访问线程的角度来看,修改不是已经完成了,就是还没开始。

         2、另一个选择是对数据结构和不变量的设计进行修改,修改完的结构必须能完成一系列不可分

    割的变化,也就是保证每个不变量保持稳定的状态,这就是所谓的无锁编程。另一种处理条件竞争的方式是,使用事务的方式去处理数据结构的更新。(STM)

        第二种是比较高阶的内容,不在讨论范围内。讨论第一种也就是使用互斥量保护数据

    三、std::mutex 创建互斥量

       1、通过调用成员函数lock()进行上锁,unlock()进行解锁。不推荐实践中直接去调用成员函数,因为调用成员函数就意味着,必须记住在每个函数出口都要去调用unlock(。C++标准库为互斥量提供了一个RAII语法的模板类--自解锁 std::lock_guard ,在其作用域内:其会在构造的时候提供已锁的互斥量,并在析构的时候进行解锁,从而保证了一个已锁的互斥量总是会被正确的解锁。      #include <mutex>头文件  

       lock_guard 介绍https://blog.csdn.net/menggucaoyuan/article/details/40985763

    2、条件竞争---保护数据

        使用互斥量来保护数据,并不是仅仅在每一个成员函数中都加入一个 std::lock_guard 对象那
    么简单;一个迷失的指针或引用,将会让这种保护形同虚设。不过,检查迷失指针或引用是
    很容易的,只要1)没有成员函数通过返回值或者输出参数的形式向其调用者返回指向受保护数
    据的指针或引用,2)成员函数没有通过指针或引用的方式来调用外部没有被保护的函数或者变量,数据就是安全的。

    class some_data
    {
       int a;
       std::string b;
    public:
      void do_something();
    };
    class data_wrapper { private:   some_data data;   std::mutex m; public:   template<typename Function>   void process_data(Function func)//传入函数func   {     std::lock_guard<std::mutex> l(m);     func(data); // 1 传递“保护”数据给用户函数   } };
    some_data
    * unprotected; void malicious_function(some_data& protected_data) {   unprotected=&protected_data; //此时函数是无保护的 }
    data_wrapper x;
    void foo() {   x.process_data(malicious_function); // 2 传递一个恶意函数   unprotected->do_something(); // 3 在无保护的情况下访问保护数据,unprotected是some_date类型的 } //相当于在保护机制下,函数访问了一个没有被保护的外部函数.也就破坏了保护了
    //意味着foo能够绕过保护机制将函数 malicious_function 传递进func()!

       3、函数接口处的竞争

        

    stack<int> s;
    if (! s.empty()){ // 1
      int const value = s.top(); // 2
      s.pop(); // 3
      do_something(value);
    }

    两个线程在执行上面同一段代码时,可能会交错的执行2、3.如此带来的竞争会访问到不同的值。虽然对里面数据进行了加锁,但是接口没有加锁。一种实现方式是用同一个锁,锁住2和3,但是得手动调用成员函数锁,不能用自解锁。

        而且,可能会在2之后又异常抛出,无法捕获。(很多情况太复杂)可以将top()和pop()封装到一个函数里面去,用锁保护调用。若传参不方便也可重载pop(),里面包含top()。

    4、死锁   

    一个给定操作被两个或两个以上的互斥量竞争时:死锁。与条件竞争完全相反——不同的两个线程会互相等待,从而什么都没做。std::lock ——可以一次性锁住多个(两个以上)的互斥量,并且没有副作用(死锁风险)。

        避免嵌套锁;避免在持有锁时调用用户提供的代码  ;使用固定顺序获取锁

       产生死锁的四个必要条件:

      (1) 互斥条件:一个资源每次只能被一个进程使用。
      (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
      (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺(有的可以情形剥夺的,              比如内存抢占)。
      (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系,资源之间相互依赖。

      避免死锁就是从破坏上面四个必要条件的角度思考。

               由于资源互斥是资源使用的固有特性是无法改变的。

    1. 破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
    2. 破坏”请求与保持条件“:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源,运行结束再回收资源。第二种是动态分配即每个进程在申请所需要的资源时,暂时放弃占用系统资源,若再需要就尝试在一次重新分配所有资源。
    3. 破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程(不一定连续编号请求)。这样就不能形成环了,较大编号的不可能请求到小编号的资源。

    5、嵌套锁:(不是很理解)

       当一个线程已经获取一个 std::mutex 时(已经上锁),并对其再次上锁,这个操作就是错误
    的。然而,在某些情况下,一个线程尝试获取同一个互斥量多次,而没有对其进行一次释放是可以的,库提供了 std::recursive_mutex 类。

      有时成员函数会调用另一个成员函数,这种情况下,第二个成员函数也会试图锁住互斥量,这就会导致未定义行为的发生。“变通的”解决方案会将互斥量转为嵌套锁,第二个成员函数就能成功的进行上锁,并且函数能继续执行。这种也不好,不如将第二个函数加到类的私有成员,让其他成员函数对其调用。(尽量实现原子操作)

  • 相关阅读:
    HTML5 WebAudioAPI-实例(二)
    HTML5 WebAudioAPI简介(一)
    HTML5 <Audio/>标签Api整理(二)
    HTML5 <Audio>标签API整理(一)
    CSS3 box-sizing 属性
    CSS3新增Hsl、Hsla、Rgba色彩模式以及透明属性(转)
    CSS3 颜色值HSL表示方式&简单实例
    C#使用Process类调用外部程序(转)
    乐视手机查看运行内存方法、EUI(Eco User Interface)乐视系统查看手机运行内存方法
    HTML5媒体播放说明
  • 原文地址:https://www.cnblogs.com/huangfuyuan/p/9127509.html
Copyright © 2011-2022 走看看