并发环境下最常用的同步手段是互斥锁和读写锁,例如pthread_mutex和pthread_readwrite_lock,常用的范式为:
void ConcurrencyOperation() {
mutex.lock();
// do something
mutex.unlock();
}
这种方法的优点是:
- 编程模型简单,如果小心控制上锁顺序,一般来说不会有死锁的问题;
- 可以通过调节锁的粒度来调节性能。
缺点是:
- 所有基于锁的算法都有死锁的可能;
- 上锁和解锁时进程要从用户态切换到内核态,并可能伴随有线程的调度、上下文切换等,开销比较重;
- 对共享数据的读与写之间会有互斥。
无锁编程(严格来讲是非阻塞编程)可以分为lock free和wait-free两种,下面是对它们的简单描述:
- lock free:锁无关,一个锁无关的程序能够确保它所有线程中至少有一个能够继续往下执行。这意味着有些线程可能会被任意的延迟,然而在每一个步骤中至少有一个线程能够执行下去。因此这个系统作为一个整体总是在前进的,尽管有些线程的进度可能没有其它线程走的快。
- wait free:等待无关,一个等待无关的程序可以在有限步之内结束,而不管其它线程的相对执行速度如何。
- lock based:基于锁,基于锁的程序无法提供上面的任何保证,任一线程持有了某互斥体并处于等待状态,那么其它想要获取同一互斥体的线程只有等待,所有基于锁的算法无法摆脱死锁的阴影。
简单的说CAS利用了CPU的硬件锁来实现对共享资源的串行使用。它的优点是:
- 开销较小:不需要进入内核,不需要切换线程;
- 没有死锁:总线锁最长持续为一次read+write的时间;
- 只有写操作需要使用CAS,读操作与串行代码完全相同,可实现读写不互斥。
缺点是:
- 编程非常复杂,两行代码之间可能发生任何事,很多常识性的假设都不成立。
- CAS模型覆盖的情况非常少,无法用CAS实现原子的复数操作。
与之不同的是,mutex lock是建立在操作系统给的特殊指令上的一种软件解决方法。而在性能层面上,CAS与mutex/readwrite lock各有千秋,简述如下:
- 单线程下CAS的开销大约为10次加法操作,mutex的上锁+解锁大约为20次加法操作,而readwrite lock的开销则更大一些。
- CAS的性能为固定值,而mutex则可以通过改变临界区的大小来调节性能;
- 如果临界区中真正的修改操作只占一小部分,那么用CAS可以获得更大的并发度。
- 多核CPU中线程调度成本较高,此时更适合用CAS。
参考博客:
1、http://ifeve.com/cas-skiplist/ 使用CAS实现无锁的SkipList
2、https://blog.csdn.net/boyxiaolong/article/details/43209919 linux下mutex与atomic性能比较
3、https://blog.csdn.net/wag2765/article/details/84794067 [C++11]std::mutex和std::atomic的性能测试对比(benchmark)
4、https://blog.csdn.net/zzulp/article/details/6259866 无锁编程与有锁编程的性能对比与分析
5、http://ifeve.com/why-is-wait-free-so-important/ 为什么无等待如此重要
6、https://www.jianshu.com/p/baaf53d69b51 非阻塞算法:关于lock-free和wait-free的一些思考
7、https://www.cnblogs.com/stevenczp/p/6611041.html 阻塞,无锁,无等待的区别
8、https://www.jianshu.com/p/4d8e56461f7b 《操作系统概念》笔记 临界区问题 - TSL & mutex lock