目录
一、linux锁的种类
- 读写锁
-
以下内容摘自《Linux中的各种锁及其基本原理》
-
读写锁也叫共享互斥锁:读模式共享和写模式互斥,本质上这种非常合理,因为在数据没有被写的前提下,多个使用者读取时完全不需要加锁的。读写锁有读加锁状态、写加锁状态和不加锁状态三种状态,当读写锁在写加锁模式下,任何试图对这个锁进行加锁的线程都会被阻塞,直到写进程对其解锁。
读优先的读写锁:读写锁rwlock默认的也是读优先,也就是:当读写锁在读加锁模式先,任何线程都可以对其进行读加锁操作,但是所有试图进行写加锁操作的线程都会被阻塞,直到所有的读线程都解锁,因此读写锁很适合读次数远远大于写的情况。这种情况需要考虑写饥饿问题,也就是大量的读一直轮不到写,因此需要设置公平的读写策略。在一次面试中曾经问到实现一个写优先级的读写锁,感兴趣的可以想想如何实现。
-
- 自旋锁
- 类似try锁,不停的去尝试解锁,需要手动的usleep以下,有网友测试,usleep 1ms or 10ms性能最高,看实际场景吧
- 互斥锁
- 无锁时,阻塞
- RCU锁
- 以下内容摘自《Linux中的各种锁及其基本原理》:
- RCU锁是读写锁的扩展版本,简单来说就是支持多读多写同时加锁,多读没什么好说的,但是对于多写同时加锁,还是存在一些技术挑战的。RCU锁翻译为Read Copy Update Lock,读-拷贝-更新 锁。Copy拷贝:写者在访问临界区时,写者将先拷贝一个临界区副本,然后对副本进行修改;Update更新:RCU机制将在在适当时机使用一个回调函数把指向原来临界区的指针重新指向新的被修改的临界区,锁机制中的垃圾收集器负责回调函数的调用。更新时机:没有CPU再去操作这段被RCU保护的临界区后,这段临界区即可回收了,此时回调函数即被调用。从实现逻辑来看,RCU锁在多个写者之间的同步开销还是比较大的,涉及到多份数据拷贝,回调函数等,因此这种锁机制的使用范围比较窄,适用于读多写少的情况,如网络路由表的查询更新、设备状态表更新等,在业务开发中使用不是很多。
- 可重入锁和不可重入锁
- 递归锁recursive mutex 可重入锁(reentrant mutex)
- 非递归锁non-recursive mutex 不可重入锁(non-reentrant mutex)
- Tips:
- Linux下的pthread_mutex_t锁默认是非递归的。在Linux中可以显式设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t设为递归锁避免这种场景。
二、无锁操作
gcc bulid-in的一些API:
原子操作:
type __sync_fetch_and_add (type *ptr, type value, ...);
type __sync_fetch_and_sub (type *ptr, type value, ...);
type __sync_fetch_and_or (type *ptr, type value, ...);
type __sync_fetch_and_and (type *ptr, type value, ...);
type __sync_fetch_and_xor (type *ptr, type value, ...);
type __sync_fetch_and_nand (type *ptr, type value, ...);
type __sync_add_and_fetch (type *ptr, type value, ...);
type __sync_sub_and_fetch (type *ptr, type value, ...);
type __sync_or_and_fetch (type *ptr, type value, ...);
type __sync_and_and_fetch (type *ptr, type value, ...);
type __sync_xor_and_fetch (type *ptr, type value, ...);
type __sync_nand_and_fetch (type *ptr, type value, ...);
type可以是如下类型:
__sync_fetch_and_add,速度是线程锁的6~7倍
type可以是1,2,4或者8字节长度的int类型,即:
int8_t
uint8_t
int16_t
uint16_t
int32_t
uint32_t
int64_t
uint64_t
关于type不需要特别声明,直接使用即可
样例代码如下:
#include <stdio.h>
int main(int argc, char **argv)
{
int n=10;
__sync_fetch_and_add(&n, 1);
printf("++%d\n",n);
__sync_fetch_and_sub(&n, 1);
printf("--%d\n",n);
__sync_fetch_and_xor(&n, 1);
printf("m^n:%d\n",n);
return 0;
}
结果如下:
++11
--10
m^n:11
三、无锁化编程
//以下两个函数提供原子的比较和交换, 如果*ptr = oldValue, 就将newValue写入*ptr
//第一个函数在相等并写入的情况下返回true
//第二个函数返回操作之前的值
bool __sync_bool_compare_and_swap(type* ptr, type oldValue, type newValue, ....);
type __sync_val_compare_and_swap(type* ptr, type oldValue, type newValue, ....);
//将*ptr设为value并返回*ptr操作之前的值
type __sync_lock_test_and_set(type *ptr, type value, ....);
//置*ptr为0
void __sync_lock_release(type* ptr, ....);
PHP的自旋锁,使用了 __sync_bool_compare_and_swap
四、部分原子操作源码
以上图片转自:https://www.zhihu.com/question/280022939
五、参考资料:
1.
gcc中内置的原子操作__sync_fetch_and_add的实现在哪个文件里?
https://www.zhihu.com/question/280022939
https://www.cnblogs.com/TMesh/p/11730847.html
3.Linux 自旋锁介绍:
https://www.cnblogs.com/chenpingzhao/archive/2015/12/13/5043746.html