手工打造一把锁

简介: 接上篇(https://yq.aliyun.com/articles/59034 ),我们知道了lock的意义。回到之前的多线程加法操作,当然也可以通过pthread提供的互斥锁来保证结果是正确的。那互斥锁本身是如何保证原子性的呢?当然首先获得锁的操作需要是一个指令,而不能用加载-比对-存储这种类.

接上篇(https://yq.aliyun.com/articles/59034 ),我们知道了lock的意义。回到之前的多线程加法操作,你也可以通过pthread提供的互斥锁来保证结果是正确的。但互斥锁本身是如何保证原子性的呢?当然首先获得锁的操作需要是一个指令,而不能用加载-比对-存储这种类似的三条指令;其次,如果是多核系统上,这个指令本身还需要加上lock前缀。x86提供了cmpxchg指令(http://x86.renejeschke.de/html/file_module_x86_id_41.html ),这类指令有个名词,就是CAS(Compare-and-swap, https://en.wikipedia.org/wiki/Compare-and-swap )便是许多锁操作实现的基础。一个最简单的实现方案大概如下(x86-64, gcc):


typedef volatile unsigned long lock_t;

void foobar_lock(lock_t *lock)
{
    unsigned long old   = 0;
    unsigned long new = 1;
    unsigned char result;
    while (1) {
        asm volatile(
            "lock \n"
            "cmpxchgq  %3, %1 \n"
            "sete      %0\n"
            : "=a" (result) : "m" (*lock), "a" (old), "r"(new) : "cc", "memory");
        if (result) {
            return;
        }
        sched_yield();
    }
}

void foobar_unlock(lock_t *lock)
{
    *lock = 0;
}

cmpxchg会比较rax寄存器的内容,和指定内存的内容,如果相等,会设置ZF=1,并将内存的内容改写为指定的其他内容;否则,将设置ZF=0,并将指定内存位置的内容复制到rax寄存器。而这些是在一条指令内完成的。

对于互斥锁的实现而言,初始为0,已锁为1,通过比较锁所在内存和0,相等则将其设置为1,并设置ZF=1,表示加锁成功。然后我们通过sete来获得ZF的值来判断锁的结果,失败则继续尝试。

之所以需要将0和1先存储到unsigned long,而不是"a"(0)这种立即数的形式,因为发现即便在64位平台,对于这种形式,gcc分配的寄存器是eax,而不是rax。

然后写一段测试代码,代码和上篇文章类似,结果正常:

images55

如果你把lock前缀去掉,同时通过taskset设置单个核心,那么也没有问题。

但是如果去掉了lock前缀,同时又允许多个核心运行,有可能结果小于正确数字,有可能程序hang住。前者可以理解,由于没有lock前缀,会产生两个线程都认为自己加锁成功了,进而同时进入了临界区;后者的情况相对复杂,通过gdb attach到那个进程去,会发现两个线程内部,看到的锁所在的内存,都是1,然额还不知道这是缓存一致性导致的问题,还是乱序执行导致的问题。

目录
相关文章
|
7月前
|
存储 监控 安全
解锁ThreadLocal的问题集:如何规避多线程中的坑
解锁ThreadLocal的问题集:如何规避多线程中的坑
362 0
|
7月前
|
存储 人工智能 关系型数据库
10个行锁、死锁案例⭐️24张加锁分析图🚀彻底搞懂Innodb行锁加锁规则!
10个行锁、死锁案例⭐️24张加锁分析图🚀彻底搞懂Innodb行锁加锁规则!
|
4月前
|
Java
什么是 CAS(自旋锁)? 它的优缺点? 如何使用CAS实现一把锁?
该博客文章解释了什么是CAS(自旋锁),包括CAS的基本概念、实现原理、优缺点,以及如何使用CAS实现锁的逻辑,并提供了使用CAS实现锁的Java完整代码示例和测试结果。
什么是 CAS(自旋锁)? 它的优缺点? 如何使用CAS实现一把锁?
|
4月前
|
NoSQL Java Redis
基本锁的理解(待补充)
可重入锁允许同一线程多次获取同一锁而不致死锁;不可重入锁则不允许递归锁定,连续调用会致死锁。死锁发生在多进程争夺资源导致僵局。读锁允许多线程并发读取,写锁则排他。自旋锁通过循环等待获取锁;共享锁用于只读操作;排它锁用于数据修改;闭锁延迟线程直至状态终止;信号量控制对资源的访问,未获信号量的线程会进入睡眠状态。
|
4月前
|
Java 开发者
解锁Java并发编程的秘密武器!揭秘AQS,让你的代码从此告别‘锁’事烦恼,多线程同步不再是梦!
【8月更文挑战第25天】AbstractQueuedSynchronizer(AQS)是Java并发包中的核心组件,作为多种同步工具类(如ReentrantLock和CountDownLatch等)的基础。AQS通过维护一个表示同步状态的`state`变量和一个FIFO线程等待队列,提供了一种高效灵活的同步机制。它支持独占式和共享式两种资源访问模式。内部使用CLH锁队列管理等待线程,当线程尝试获取已持有的锁时,会被放入队列并阻塞,直至锁被释放。AQS的巧妙设计极大地丰富了Java并发编程的能力。
51 0
|
安全 算法 Java
可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用
可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用
|
7月前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
71 0
|
存储 安全 Java
08.从源码揭秘偏向锁的升级
大家好,我是王有志。上一篇学习了synchronized的用法,今天我们深到synchronized的原理,来学习偏向锁升级到轻量级锁的过程。
175 0
08.从源码揭秘偏向锁的升级
|
存储 Java C++
【全网最细系列】synchronized锁详解,偏向锁与锁膨胀全流程
【全网最细系列】synchronized锁详解,偏向锁与锁膨胀全流程
595 0
|
SQL Java 数据库连接
数据库相关锁总结(共享锁,排它锁,更新锁,意向锁,计划锁),看完这篇将会对锁产生更深的理解
数据库相关锁总结(共享锁,排它锁,更新锁,意向锁,计划锁),看完这篇将会对锁产生更深的理解
116 0