(承接上一篇)
3、更自由一些的mutex管理类:unique_lock
这个管理类在设计的时候考虑的更多的是使用锁的自由度,
- 具有lock_guard的所有功能,
- 而且提供随时lock和unlock的能力,效率上应该比lock_guard有所缺乏。
在设计时感觉参考了unique_ptr的思想, - 可以将管理类与mutex锁解关联,
std::mutex m;
std::unique_lock lk(m); //获取锁并lock
//or
std::unique_lock lk(m,std::defer_lock); //关联锁,先不要lock(待会自己手动lock)
//or
std::unique_lock lk(m,std::adopt_lock); //关联锁,不要再lock(用于关联前已经lock了锁)
unique_lock其实没什么好说的地方,跳了
4、shared_lock
说到这个锁管理类,就得说一下另一种mutex了,叫shared_mutex。
shared_mutex实际上是一种读写锁,对共享数据进行并发读时,实际上可以多个线程一块访问,只有写操作时需要提供互斥的环境。
对于读写锁,当没有写线程访问时,读线程可以同时访问共享数据,也就是读线程之间是不互斥的。而有写线程访问时,其他的读线程和其他的写线程都将被block,以保护共享数据的完整性。
所以读/写线程的代码通常是这样的:
std::shared_mutex m;
void read()
{
std::shared_lock lk(m);
read_shared_data();
}
void write()
{
std::lock_guard lk(m);
write_shared_data();
}
题外话
读写锁的性能一般和并发性能息息相关,一般有这么两种读写锁:
- 读友好型读写锁
- 写友好型读写锁
第一种读写锁的特点是,写线程必须在所有读线程都放弃锁的情况下才能对数据进行修改。这种锁的问题其实稍加分析就显而易见,如果很多读线程不断地交叠式的读数据,那么写线程可能要一直等待而得不到锁去更新数据。
第二种读写锁的特点是,当有写线程准备写数据时,出了当前已经开始读取的读线程外,其余新来的读线程都会被阻塞,直到所有正在读的读线程都完成任务后,写线程进入修改数据,然后再放行所有读线程。
经过测试,c++实现的是第一种读写锁,但其实我个人觉得第二种读写锁的用途会大一些,因为当数据要进行更改时,一般说明原有数据可能已经过时,这个时候过时的数据被读走的意义也许就不是很大了。
明天有时间更新一下写友好型的读写锁设计,以及linux内核中的一种【强·写友好型】读写锁,那种读写锁的基本设计思想是,只要有写线程要写,写线程可以立马进行数据的修改,不管有没有正在读的读线程(当然依然与其他写线程互斥),由读线程读完之后检测,如果自己在读的过程中有写操作发生,则读线程自觉重读。
以下附c++读写锁类型测试:
可以看到10ms时,写线程企图获取锁,但由于读线程源源不断交叠访问,所以直到最后读线程都结束了,55ms时写线程才拿到锁。