Daily C/C++ 并发编程基础
今天这篇文章是简要的介绍一下并发编程中的用于控制同步的对象
参考文章是CMU15445的project4中andy推荐的一篇文章
锁
锁是一个抽象的概念,用来保护资源的,当你持有锁的时候,你就可以去访问受保护的资源。当你没有持有锁的时候,你就不能去访问对应的资源
锁本身的含义也隐喻了独占的意味。当你尝试去获取一个锁,却失败的时候,要么你会阻塞住,一直等到你可以获得这个锁为止。要么你会返回一些错误码等东西来表示获取失败
最常见的一种就是简单的计数,一个锁可以有一个上限的值。当我们获得这个锁的时候,计数就加一。当我们释放这个锁的时候,计数就减一。当计数达到上限的时候,我们就不能再获得这个锁。
互斥锁
Mutex,在C++中大家应该也遇到过,他的全程是mutal exclusion,即互斥。
就可以简单的理解成前面介绍的锁,上限值是1而已。
一个互斥锁同一时间只能被一个线程所持有,只有获得锁的那个线程才能去解锁(这里和下面要介绍的信号量不同)
当互斥锁被锁住的时候,任何尝试去获得这个互斥锁的操作都会失败或阻塞,即便是同一个线程也不行
递归互斥锁
这个就是相同的线程可以多次获得同一个互斥锁,但是我们也要保证释放相同的次数
这个我个人基本没用过,可以肯定的是,在大多数的情况下,递归的互斥锁不是我们想要的东西。我们可以通过普通的互斥锁来帮我们保护资源。
共享互斥锁
也被称为读写锁,读写锁有两种锁定模式。
共享锁定下,允许其他申请共享锁定的线程获取这个锁。因为多个线程同时读取一个资源是没问题的
排他锁定下,不允许其他线程获得任何模式的锁,因为同一时间只能有一个线程修改资源
自旋锁
自旋锁就是一种特殊的互斥锁,他不使用操作系统的同步功能,他会在一个循环中不断的获取锁(比如利用CAS或者TAS等原子操作),就像是我们不断的在尝试旋转这个锁,直到打开为止
信号量
信号量是一种限制较少的锁,一个信号量有一个预定义的最大值,以及一个当前值。
当我们尝试获取这个锁的时候,称为P,他就会让当前的信号量减一。
当我们释放这个锁的时候,称为V,他就会让当前的信号量加一。
当信号量为0的时候,我们尝试去获取这个锁,我们就会被阻塞住。
当我们去释放一个锁的时候,如果有线程在等待,那么我们就会唤醒一个正在等待的线程。
这里和互斥锁不同的地方在于,互斥锁只有获得锁的那个线程才能释放那个锁。但是对于信号量来说,任何线程都可以在任何时间去对信号量进行V操作
临界区
当我们在持有一个锁的时候,这段代码就被成为是临界区。如果熟悉windows编程的同学应该可以知道,windows中使用临界区来做互斥。
C++中的互斥锁
std::mutex
std::timed_mutex
std::recursive_mutex
std::recursive_timed_mutex
std::shared_mutex
等等
基本上就是一个mutex,然后使用各种的修饰,比如recursive是递归的,timed是有一个超时的判定,shared就是刚才说的读写锁了
具体的大家可以去cppreference上看
C++还有一些用来持有锁的对象,利用RAII帮我们维护锁
这些对象会在构造的时候申请锁,析构的时候释放锁
最常用的就是std::lock_guard<>
了
C++中并没有支持信号量,但是C++中支持原子类型的对象,我们可以自己按照逻辑并配合原子对象来编写信号量
C++中还有一个是条件变量,他可以让线程在条件变量上等待,直到其他的线程在这个条件变量上唤醒他。他会去检查是否满足等待的条件并决定是继续执行还是继续等待。
之后我会再出一个有关条件变量的文章
文章评论