互斥体

读书笔记《C++并发编程实战》(3) - 线程间数据共享

|▌冷眼眸甩不掉的悲伤 提交于 2019-12-01 02:11:39
竞争条件: 进程中各个线程可共享数据,既是优点也是缺点,而导致不同线程操作数据出现异常的情况时,竞争条件是其中之一。 避免竞争条件: 一:采用保护机制,封装正在修改的或访问的数据结构,确保其他线程在共享数据被修改前或修改后才可见可访问。 二:修改数据结构设计、或者使用不变量,如无锁编程(无锁数据结构等)。 互斥体保护: 互斥体作为同步原语,可确保一个线程获取共享数据时锁定资源,并处理资源后解锁,其他线程才可见可访问该资源。 不过需要精心处理互斥体,确保不会出现死锁、保护过多、过少的数据。 std::mutex封装了基本的锁操作,此外使用std::lock_guard类模板实现互斥体的RAII惯用法,以避免显式调用lock/unlock等接口。 另外互斥体一般与受保护的共享数据放在一起,如同一个类中,且需要精心设计避免受保护的数据外泄(如:抛出地址或引用或者传入 了其他可访问受保护的共享数据的外部函数等)。线程已持有std::mutex锁时,若该线程再次获取该锁将出现异常,此时应使用 递归锁std::recursive_mutex。 死锁: 一般在两个或者多个线程拥有对方的锁资源时却相互等待另一方释放所拥有的锁资源。进而产生死锁。这个情况常出现在需要锁定两个 或者更多互斥体或其他原语时以执行操作时最常出现的情形。其他的情形也包括等待相互的另一方线程退出或者其他资源或同步需求时。

读书笔记《C++并发编程实战》(6) - 基于锁的并发数据结构

流过昼夜 提交于 2019-12-01 02:11:23
数据结构: 作为程序设计问题中的关键部分,并行程序设计问题也需要处理数据结构,数据结构本身要么不需要同步,要么通过同步机制来保障如互斥体、锁等; 并发设计含义: 多个线程可同时使用此数据结构且执行相同或不同的操作,满足每个线程针对数据结构有一致性的视图,不会破坏或者丢失数据等竞争条件,则为线程安全的。 数据结构的操作接口中若抛出了异常,但并没有修改数据结构,则认为是异常安全的。 加锁的过程可能导致序列化问题,对性能有一定的限制、损失:即多个线程只能轮流线性而不是并发地存取被互斥体等锁保护的数据;在数据结构设计中实现并发需考虑并发的范围, 设计的基本思想是:更小的保护区域,更少的操作被序列化,以及更高的并发潜能。 并发设计数据结构的准则: 保证存取数据时安全的:除了需要考虑必要的接口提供锁保护,还需要考虑调用其他接口或者内部函数是否与其他线程同时调用操作可以是安全的,确定不变量。 允许真正的并发存取操作:考虑锁的范围是否可尽可能小、数据结构的不同部分是否可被不同的互斥体等保护、是否所有操作需要同样级别的保护等。 基于锁保护的数据结构: 确保存取数据时要锁住正确的互斥体且要确保锁住的时间最小化。另外若对数据结构的独立部分用独立的锁来保护可提高并发性, 但此时若设计不当可能出现多个锁的操作引起死锁,此相对使用一个互斥体锁来操作要复杂精细一些。

同步 互斥体和信号量

二次信任 提交于 2019-11-27 01:09:39
1、互斥体和信号量都是为了实现同步,但是二者解决的问题不一样,也就是说应用场景不一样。 2、互斥体通过加锁,对于共享的资源,大家排队,依次去访问,一个一个来。也就是说,任何时刻只有一个线程访问,其他的线程等待。 3、互斥体加锁存在的问题:无法控制线程的访问顺序。考虑两个线程A,B,访问顺序可能是A-B,也可能是B-A,就要看谁先加锁。 4、考虑下面的场景,生产者/消费者模式,假定A是生产者,B是消费者,生产一个,消费一个,循环下去,这样就要求访问顺序必须是A-B-A-B... 5、怎么解决这个问题?   使用信号量,初始化,信号量的容量是1,可用资源为0。A进行V操作,资源加1,B进行P操作,资源减1。A进行V操作的时候,如果可用资源为1(容量满了),必须等待,直到B消费掉资源。B进行P操作的时候,如果可用资源为0(没有资源可用),必须等待,直到A生产出一个资源。通过这种方式控制线程访问顺序。 6、这里可以看出二者的区别:   a、互斥体:大家排队,一个一个来。信号量:控制访问顺序,当前线程检查条件,条件不满足,等待其他的线程去操作,使条件满足。   b、对互斥体加锁,必须是谁加锁谁释放。而对信号量PV操作,是不同线程之间可以进行成对的PV操作。   c、可以使用信号量的PV操作来模拟互斥体的加锁,信号量容量为1,可用资源为1,每个线程都先P操作,do sth ,再V操作