• 认真地记录技术中遇到的坑!

C++11 线程库 第二节 在线程间共享数据

C/C++ Moxun 8个月前 (03-05) 200次浏览 0个评论

2.1 线程之间共享数据的问题

   实际上这就是因为竞争引起的同步和互斥的问题。
   - 从总体上看,线程之间的问题都是由对数据的修改造成的。如果所有线程对共享数据的使用方式都是只读的,那这是没有任何问题的,因为一个线程所读取的数据不会受到另一个线程是否正在读取相同的数据而影响。但是如果有线程需要修改共享数据,那么此时,你将需要格外小心了。
   一个帮助程序员推导代码的概念——不变量:对于特定的数据结构总是为真的语句。这些不变量在更新中经常被打破。
   修改线程之间的最简单的潜在问题就是破坏不变量,书上举的是链表删除的例子。P32

2.1.1竞争条件

     在并发环境中,竞争条件就是结果取决于两个或更多线程上的操作执行相对顺序的一切事物,线程竞争各自的操作。在说并发时,竞争条件通常指破坏了不变量时产生的竞争
     C++标准库定义了数据数据竞争,它表示因对单个对象的并发修改而产生的特定类型的竞争条件。数据竞争会导致未定义行为。有问题的竞争通常发生在完成操作需要修改两个或者多个不同的数据块的地方,例如双向链表的节点删除问题。

2.2.2避免有问题的竞争条件

   A.用保护机制来封装你的数据结构,以确保只有实际执行修改的线程能够在不变量损坏的地方看到中间数据。从其它角度看,要么这种修改还没开始,要么已经完成。
   B.另一个选择是修改数据结构的设计及其不变量,从而令修改作为一系列不可分割的变更来完成,每个修改均保留其不变量,这 通常称为无锁编程。
   C.将对数据结构的更新作为一个事务处理,就如同在一个事务内完成数据库的更新 一样,所需的一系列数据修改和读取被存储在一个事务日志中,然后在单个步骤内提交,如果该提交因为数据结构已被另一个线程修改而无法进行,该事务重启,这叫做软事务内存(STM),C++不直接支持STM
   C++标准提供的保护共享数据的最基本机制是互斥元。

2.2用互斥元保护共享数据

   线程访问共享数据带来的一个问题是互斥,简单的说互斥就是有多个线程需要对共享数据进行读写操作,此时我们要求在同一时刻只能有一个线程访问共享数据,否则会造成读写冲突。
   一个解决互斥问题的方案是,将访问共享数据的代码块标识为互斥的,这意味着如果任何线程运行了访问共享数据的代码块,则其它线程不能在运行访问共享数据的代码块,必须等待当前正在执行的线程运行完成。这是使用互斥元的操作原语所能得到的。
   具体的操作步骤是,在访问共享数据时,先加锁(lock)互斥元,当访问共享数据结束后再解锁互斥元(unlock)。
   注:感觉上,互斥元就是标识一段代码是互斥的。
   注:这里有一个非常重要的前提条件,有助于理解后面将要讲解的关于锁的问题。线程库会确保一旦一个线程已经锁定某个互斥元,所有其它试图锁定相同互斥元的线程必须等待,直到成功锁定了该互斥元的代码解锁此互斥元。

2.2.1 使用C++中的互斥元

    在C++中通过构造std::mutex的实例来创建互斥元,调用成员函数lock来锁定它,调用成员函数unlock来对它解锁。但是如果直接调用成员函数意味着你必须格外小心,需要在代码离开的每条路径上调用unlock,包括catch子块。
    还记得RAII和在前一节中我们为了保证在发生异常时线程对象被结合或者分离所编写的thread_guard类和scoped_thread类吗?我们可以采用同样的思想,利用局部对象管理资源。C++标准库为我们提供了这样lock_guard类模板类,它在构造时对互斥元调用lock,在析构时调用unlock,这样就实现了资源的自动管理。mutex和lock_guard都在头文件中。
    ```cpp

#include
#include
#include

using namespa