自旋锁的实现及优化

2023-05-16

自旋锁也是一种互斥锁,和mutex锁相比,它的特点是不会阻塞,如果申请不到锁,就会不断地循环检测锁变量的状态,也就是自旋。自旋锁的实现算法大多使用TAS算法或者CAS算法。

TAS算法
Test And Set,顾名思义,就是测试并设置,也就是先对目标值进行检查,如果目标值符合预期的结果则把它修改为所需要的值,并返回设置前的原值。它的语义原型,可以使用下面的伪代码表示:

<< atomic >>
function tas(p : pointer to bool) returns bool {
    bool value = *p
    if !*p {
        *p ←true
    }
    
    return value
}

如果指针p指向的变量原值为false,就设置为true,并返回false;如果为true,就直接返回true。这个tas操作过程要求是一个原子操作,中间不允许被打断,也就是保证了比较操作(*p是否为false)和修改操作(*p置为true)是一个原子操作,在X86环境下,使用xchg指令来实现。

CAS 算法
Compare And Swap,把当前值与一个期望值作比较,如果相等,则更新为新值,并返回比较结果。它的语义原型,可以使用下面的伪代码表示:

<< atomic >>
function cas(p : pointer to int, old : int, new : int) returns bool {
    if *p ≠ old {
        return false
    }
    *p ← new
    return true
}

它的输入参数一共有三个,分别是:
p: 指向要修改的变量,old: 旧值,new: 新值。返回值是布尔类型,表示赋值是否成功。
CAS 实现逻辑也非常简单,就是先检查p所指位置的值是不是等于期望值old,如果等于,那就把该位置的值更新为new,并返回 true,否则就不做任何操作,返回false。同样要保证比较和修改是一个原子操作,在X86环境下,使用lock cmpxchg指令。

下面以TAS算法为例,介绍一下自旋锁的实现及其优化要点(C++11、X86环境)。

atomic_flag
在C++11版中原子操作类atomic_flag提供了TAS算法,下面使用atomic_flag实现一个自旋锁类splin_lock:

class spin_lock final
{
public:
	void lock() {
		while (flag.test_and_set(memory_order_acquire));
	}

	void unlock() {
		flag.clear(memory_order_release);
	}

private:
	atomic_flag flag;
};

使用atomic_flag类型的变量flag作为锁同步变量,在加锁函数lock()中,test_and_set()实现了TAS原语操作。当flag为true时,返回true,说明这个锁已经被占有,其它的线程无法获取了;在解锁函数unlock()中,使用clear()让flag复位0,表示释放锁。

此外,使用自旋锁时,为了保证临界区内共享变量的可见性和顺序性,还要指定内存序(memory order)保证内存一致性,即在解锁时是释放(release)语义,在加锁时是获取(acquire)语义。这样,能够保证解锁时在临界区修改的数据,在获得锁后进入临界区可见,内存操作不产生乱序现象。

实现自旋锁的关键之处是test_and_set函数,前面说过,要保证整个函数的执行是原子的,在X86环境下,它是使用xchg指令来实现的。

xchg指令
进行编译后,lock函数的汇编代码如下:(略有调整)

.L2:
        mov     eax, 1
        xchg    al, BYTE PTR sync_var
        test    al, al
        jne     .L2
        ret

lock函数里面的主要指令是“xchg al, BYTE PTR sync_var”,它是把寄存器al中的值(初始化为1),与同步变量sync_var的值进行交换。然后检查交换后al中的值,如果其值为1,说明同步变量sync_var的原值是1,应该是被别的线程成功申请锁后写入的,说明该锁已经被占用了,那就继续循环,直到交换后al中的值为0,此时sync_var同时也被置为1了,从lock函数中返回。

查看intel指令集手册,会发现xchg指令的功能是交换寄存器和内存变量中的值,显然是一个“读取-修改-写回”(RMW)的操作。尽管没有在这个指令前面加上lock前缀,但CPU在执行这个指令期间,自动实现了锁协议,执行时会有一个#lock信号产生,用来锁定内存总线,直到xchg指令执行完成,可见,该条指令是一个原子操作。我们知道,在锁内存总线期间,意味着别的CPU无法使用访问内存,显然这是为了实现硬件原子操作所付出的代价。

从上面的汇编代码可以看到,在调用lock函数申请锁时,只要lock_var变量不为0,就会无条件的循环执行xchg指令,xchg指令执行的太频繁了,以至于其它CPU的运行都要受到影响。因为一个CPU在xchg执行期间要独占内存总线,其它CPU要访问内存就得和这个CPU竞争内存总线,如果竞争不到,就得停下来等待内存总线的释放,造成CPU一定程度上处于“失速”状态,如果几个线程同时在申请锁,进一步加剧了内存总线的竞争程度。也就是在一个CPU竞争自旋锁期间,其它CPU尽管没有参与竞争,但是也受到影响,导致性能下降,对其它CPU来说,这不是一个高效的实现方案,因为它降低了系统的整体性能。
如何进行优化呢?

双检查锁机制
我们知道在实现单例模式时,有一种针对互斥锁的优化方案,叫做双检查锁机制,它的基本思想是先使用执行成本低的代码来检查条件是否满足,如果条件满足,再使用有锁的逻辑继续检查条件并进行设置,也即TAS操作。

基于这个思想,也可以使用双检查锁机制来进行优化,下面是修改后的lock函数代码,不过atomic_flag提供的成员函数test()在C++20版本才提供,需要支持C++20版本的编译器才能编译。

void lock() {
    while (true) {
        while (flag.test(memory_order_relaxed)); // 第一次检查:Test
        if (!flag.test_and_set(memory_order_acquire)) { // 第二次检查并设置:TAS操作
            break;
        }
    }
}

编译后生成的汇编代码如下(略有调整):

        mov     edx, 1
.L2:
        movzx   eax, BYTE PTR sync_var // 从内存中读取同步变量的值
        test    al, al // 判断,第一次检查 Test
        jne     .L2 // 条件不符合,回退
        mov     eax, edx
        xchg    al, BYTE PTR sync_var // 第二次检查并设置:TAS操作
        test    al, al
        jne     .L2
        ret

先看一下第一次检查,在检查时,每次都要从内存中读取变量sync_var,然后进行检查是否为0,如果不为0,则继续读取再检查,一直循环执行“读取-判断-回退”的逻辑,直到读出的sync_var为0为止。注意,这里的读取指令是“movzx eax, BYTE PTR sync_var”,是非常简单的指令,它从内存中加载一个字节的数据到寄存器al中,也是一条原子操作指令,但该指令不会锁总线。根据MESI缓存一致性协议,如果sync_var的值没有被别的CPU修改过,那么它就在当前CPU的L1 cache缓存中会一直有效,也就是只要内存中的sync_var的值没有被更新,该指令执行时就会一直从L1 cache中读取,即没有访问内存,也就不会占用内存总线,所以第一次的循环检查也被称为“本地自旋”(见汇编语句3(读取)、4(判断)、5(回退))。

当本地自旋检测到sync_var值变为0后,就退出自旋,立刻进入第二次检查,这时为什么还要进行检查呢?因为第一次检查不能原子地设置sync_var为1,所以还得要使用原子指令xchg再一次进行检查并设置,由于已经检测到sync_var为0了,这次执行xchg指令成功的几率非常高。

可见,双检查锁在自旋锁中的实现逻辑是Test-Test-And-Set,所以有时候也简称为TTAS算法。同自旋锁的TAS算法相比,TTAS算法大大减轻了对内存总线的竞争程度,也降低了xchg执行时为了维持cache一致性而产生的内存总线流量,不过,因为要进行两次检查判断,申请锁的速度没有TAS算法快,也就是TTAS算法申请锁时的延迟比TAS算法大。

pause指令
再看TTAS算法的代码实现,当第一次检查时,如果一个CPU从本地自旋中退出,说明此时已经有别的CPU把这个sync_var变量置为0了,即别的线程已经把锁释放了。当接着使用TAS操作进行第二次检查时,如果没有设置成功,说明在第一次检查结束和第二次检查开始之间出现了状况:第一种可能是执行被中断了,即有优先级更高的线程把它抢占了;第二种可能是和别的CPU在执行时间上有重叠,让别的CPU抢先获得了锁。
如果发生了第二种情况,别的CPU获得锁后要进入临界区进行业务处理,这也暗示着自旋锁不会马上被释放,那么本CPU肯定会再次进入“本地自旋”并且会持续一段时间。我们不妨估算一下大概需要自旋多长时间:如果想让CPU从本地自旋中退出,至少要等到另一个CPU执行完临界区代码,调用unlock时,把0值写入内存变量sync_var,本CPU读取sync_var变量时发现缓存和内存中的值不一致,重新从内存中加载;假设临界区没有代码,它的执行时间为0(实际上是不可能的),也就是说最少也要等待CPU一次写内存和一次读内存的时间,小于这个时间是不会从循环中退出的,这段时间CPU一直处于空转状态。同时,我们知道“本地自旋”实质上是一个“死循环”,指令流非常短,全部在L1 cache中,而且数据也在L1 cache中,执行期间几乎不访问主内存,执行速度极快,也就是CPU处于高速运转中,电量消耗非常大,显然这也是一大不足。
既然能够预见到这种情况肯定会发生,能否针对此情况进行改进呢?intel的CPU专门针对自旋等待的场景提供了一个指令:pause,该条指令的功能是给CPU一个提示:当前正在进行自旋等待,可以暂停一下,比如等待一个或几个内存总线操作周期的时间之后,然后再继续运行(因为在这期间,自旋锁是大概率获取不到的,与其让CPU自旋忙等,不如停下来休息一下)。

因为C++中没有相应的函数来执行pause指令,所以嵌入了汇编指令,下面修改后的代码:

void lock() {
    while (true) {
        while (flag.test()) {
            asm("pause");
        }
        if (!flag.test_and_set(memory_order_acquire)) {
            break;
        }
    }
}

显然这情况能够大大降低CPU执行资源和电量的消耗。不过,这种方法进一步加大了获取锁的延迟性,因为每次自旋条件不符合需要就得先pause一段时间,如果在pause期间,锁被释放了,不会及时的获取锁,只能等到CPU从pause中退出时才有可能。而且这个延迟的时间也不固定:有可能CPU刚刚开始pause,锁就释放了,显然此时延迟最大;也有可能CPU从pause退出时,恰好锁就释放了,显然此时延迟最小,这是最理想的场景。如果应用场景是对响应性能敏感的场景,就得要慎重考虑了,能否容忍使用pause指令带来的延迟损失。

同时我们也知道,CPU处于pause状态时,什么也不做,不像操作系统提供的yield命令,可以让CPU执行别的任务。让CPU资源处于闲置状态,可能会感觉有点可惜,不过在支持超线程技术的CPU架构体系中可以利用这个特点,在一定程度上提高超线程的执行速度。现代CPU大多提供了超线程技术,即在一个物理核上有两个逻辑核,它们有自己独享的寄存器,但是需要共享执行单元、系统总线和缓存等硬件资源。如果执行自旋操作的CPU是一个逻辑核,当它进入本地自旋的时候,会加剧同一个物理核中的执行资源和系统总线的竞争程度,从而影响其它逻辑核的执行效率;但是,如果一个逻辑核进入了pause状态,就不再使用系统资源了,同物理核的另一个逻辑核就可以独享物理核的全部资源了。

不过需要指出的是,当自旋锁的临界区的执行时间很短时,比如只是简单的几个内存访问和计算,可以使用自旋锁+PAUSE技术;如果临界区执行时间较为耗时,或者发生了前面说的第一种场景,即线程被抢占后中断执行了,使用pause会让一个CPU长时间处于暂停状态,也是一种资源浪费,不如把CPU分配给别的线程使用。这时可以考虑让操作系统参与进来,比如调用yield,把CPU调度给别的任务(如NGINX实现的自旋锁),或者直接把当前线程挂起,等到解锁时再唤醒它,当然这要涉及到线程上下文的切换,也就是所谓的重量级互斥锁了(如mutex锁)。

常见平台的自旋锁实现
下面是一些平台所实现的自旋锁加锁函数的代码,也采用了上面介绍的实现方法,只是形式有所不同,其中nginx还对pause的次数进行定制,执行一定次数之后还会调用yield,让出CPU的资源,大家不妨分析一下:
1、DPDK

typedef struct {
	volatile int locked; /**< lock status 0 = unlocked, 1 = locked */
} rte_spinlock_t;

static inline void
rte_spinlock_lock(rte_spinlock_t *sl)
{
	while (__sync_lock_test_and_set(&sl->locked, 1))
		while(sl->locked)
			rte_pause();
}

3、NGINX(去掉了一些不相关代码)

void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
{
    ngx_uint_t  i, n;

    for ( ;; ) {
        if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
            return;
        }

        if (ngx_ncpu > 1) {
            for (n = 1; n < spin; n <<= 1) {
                for (i = 0; i < n; i++) {
                    ngx_cpu_pause();
                }
                if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
                    return;
                }
            }
        }

        ngx_sched_yield();
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自旋锁的实现及优化 的相关文章

  • NOKOV度量动捕软件教程(6):数据分析

    1 点击界面上方 窗口分割 2 个窗口 xff1a 上 下 选项 xff0c 让主界面分割为上下两个窗格 xff08 如图 xff09 xff0c 选中窗格时会有橙色边框 xff0c 此时点击 视图类型 xff0c 让其中一个窗口显示 3D
  • crazyswarm+crazyflie+NOKOV动捕飞控方案操作说明(3):通过VRPN协议对接

    一 crazyflie配置 1 查看 crazyflie 固件更新完毕后 xff0c 取下crazyflie xff0c 并连接电池 xff0c 把crazyradio接上电脑usb口 xff0c 运行以下命令 xff1a rosrun c
  • crazyswarm+crazyflie+NOKOV动捕飞控方案操作说明(4):SDK对接

    一 crazyswarm nokov 支持 1 更换libmotioncapture crazyswarm 支持nokov首先需要更换nokov专有libmotioncapture xff0c 进入路径 xff1a cd crazyswar
  • UE5与NOKOV度量动捕系统连接教程

    目录 一 动捕软件安装与数据准备 二 插件安装与 UE5 设置 一 xff09 插件安装 二 xff09 UE5 设置 xff08 实时播放模式 使用设备 xff1a 从仔动作捕捉套装 xff08 NOKOV度量 xff09 软件 xff1
  • Unity与NOKOV度量动捕系统连接教程

    目录 一 动捕软件安装与数据准备 二 插件安装与 Unity 设置 xff08 实时播放模式 xff09 三 插件安装与 Unity 设置 xff08 后处理下实时模式 xff09 使用设备 xff1a 从仔动作捕捉套装 xff08 NOK
  • MotionBuilder与NOKOV度量动捕系统连接教程

    目录 一 动捕软件安装 二 数据录制 导入与导出 xff08 一 xff09 创建 Markerset xff08 二 xff09 数据采集 xff08 三 xff09 数据导入 xff08 四 xff09 数据导出 三 插件安装与 Mot
  • NOKOV动作捕捉系统使多场协同无人机自主建造成为可能

    近年来 xff0c 工业机器人的兴起使得建造的效率和安全性得以提升 xff0c 但由于机器人由于大小与活动范围的限制 xff0c 在大型建筑上难以施展拳脚 上海同济大学建筑系的无人机自主建造小组 xff0c 正在进行以无人机取代工业机器人进
  • 多智能体系统集群协同控制实验平台详解与典型案例

    目录 一 机器人实验是智能体集群研究必要手段 二 动作捕捉系统解决智能体集群实验系统多个痛点 三 多智能体集群协同控制实验平台 1 Crazyswarm多无人机集群编队实验平台 2 Robotarium机器人平台 3 中科院自动化所智能集群
  • NOKOV度量动作捕捉协助完成无人机室内定位研究

    随着工业发展 技术进步 xff0c 无人机的使用在各行各业愈发普遍 xff0c 开始出现无人机飞行送外卖 智能无人机自主巡检等多方面应用 在这一过程中 xff0c 无人机飞行定位就成为了重中之重 西北工业大学无人机特种技术国防科技重点实验室
  • 光学动作捕捉系统构成

    一套光学动作捕捉系统由红外动作捕捉镜头 动作捕捉软件 反光标识点 POE交换机 和若干配件组成 xff08 如标定框和镜头固定装置等 xff09 其本质是定位系统 xff0c 通过计算分析 xff0c 来获取与其相关的速度 加速度等多种运动
  • vscode命令行起本地服务,可发送http请求

    在我们vscode中默认打开的是file协议 xff0c 但是往往我们会有ajax等请求 xff0c 需要发送http等其他协议 xff0c 所以我们需要搭起本地服务器 xff1a 1 xff1a cd 进去到文件位置 xff0c 运行 n
  • 动作捕捉用于仿生机器人的运动规划

    随着机器人 三维动画 虚拟现实等产业的发展 xff0c 关于仿生机器人的动作研究早已成为重要的热点课题 如何让机器人或虚拟人物做出合理 流畅的姿态呢 xff1f 这就要涉及到逆运动学算法研究 人体很复杂 xff0c 传统算法需优化 由于人体
  • 智能化人机协作 遮挡情况下准确识别目标信息

    研究背景 废旧产品 xff08 end of life products xff09 的拆卸是工程全生命周期管理的一个基本步骤 在减少资源消耗和温室气体排放的同时 xff0c 回收可重复使用的部件可能创造相当的经济价值 xff0c 同时也能
  • 线下·香港 | 工业大数据与智能系统前沿会议

    由香港理工大学主办的工业大数据与智能系统前沿会议将于2023年4月28日至5月1日在香港举行 届时来自海外 内地及香港的知名科学家将聚首 xff0c 将围绕大会主题 面向人机共融的工业转型 发表演讲 xff0c 分享他们的独到见解并探讨最新
  • 人机耦合模型研究及其在下肢外骨骼机器人设计中的应用

    在外骨骼研究中 xff0c 一个合适的人机耦合模型非常重要 xff0c 它可以帮助预测外骨骼系统直接作用于人体产生的影响 xff0c 避免不必要的伤害和能量损失 xff0c 同时也有助于优化外骨骼系统的设计和控制 xff0c 提高其佩戴的舒
  • STM32】 DMA原理,步骤超细详解,一文看懂DMA

    如需转载请注明地址 xff1a https blog csdn net as480133937 article details 104927922 DMA的基本介绍 什么是DMA DMA的基本定义 DMA xff0c 全称Direct Me
  • float类型数据在报文中的传输方法

    方法1 xff1a 转化成整型传输 假如保留float类型数据为两位小数 xff0c 我们可以将float数据 100 转换成整型数据传输 xff0c 对端收到后 xff0c 再 100 xff0c 转换成float类型 方法2 xff1a
  • 101、104规约解析

    转载 xff1a 电网101 104规约解析 xff08 Java xff09 张二狗和苗翠花的博客 CSDN博客 iec101 java 1 前言 最近在研究广东电网的101与104规约 xff0c 也就是DL T634 5101 200
  • Ubuntu:Python多版本切换。

    使用 update alternatives更改系统Python版本 1 查看可选版本 sudo update alternatives list python 如果提示 xff1a update alternatives error no

随机推荐

  • ROS(melodic)安装问题汇总及解决方法

    终于装上了ROS xff0c 费了很大的波折 xff0c 基本上能遇到的问题都遇到了 xff0c 记在这里希望能给遇到同样问题的朋友一点参考 首先是在虚拟机上装ubuntu 18 04 xff0c 这个没什么问题 xff0c 所用的镜像文件
  • Http请求出现invalid http response问题的原因分析

    发生场景 xff1a A系统发送Http请求调用B系统提供的接口 xff1b 发生问题 xff1a A系统报错 xff0c 提示 invalid http response 错误信息 xff1b 问题分析 xff1a 根据翻译 xff0c
  • STM32利用CUBEMX建立自定义HID工程,并且完成64字节的IN,OUT传输功能。

    STM32 Customed HID开发流程 本文介绍的是STM32的cubeMX自定义HID的开发流程 cubeMX配置customed HID模式 更多详细配置壳查看代码CubeMX的配置文件 修改usbd custome hid if
  • STM32 uart 单线半双工模式(cube版本)

    STM32 uart 单线半双工模式 xff08 cube版本 xff09 1 引言 在某些场合下需要进行三线制串口通信 xff08 信号线只有一根 xff09 xff0c 这就要求进行单线半双工的模式进行通信 在这种情况进行数据协议传输的
  • AS5600磁编码器开发记录

    AS5600使用简介 xff08 程序员版 xff09 本文由 智御电子 提供 xff0c 同时提供范例教程 xff0c 以便电子爱好者交流学习 前言 xff1a 最近由于工作需要接触到AS5600这颗磁角度传感器 xff0c 以前就对相关
  • STM32 硬件UART接收超时检测设置

    STM32 硬件UART接收超时检测设置 本文作者 智御电子 xff0c 期待与电子爱好者交流学习 应用场景 在uart应用中有时候需要进行双工通信 xff0c 主机需要对从机的数据进行接收超时检测 xff0c 例如modbus协议 xff
  • 发送GET请求 示例

    发送GET请求 64 param url 请求地址 64 param param 请求参数 64 param headers 64 return private String requestByGet String url Map lt S
  • STL不同容器的优缺点

    一 容器的分类 1 序列容器 xff08 1 xff09 vector 典型的序列容器 xff0c 任意元素的读取 修改具有O 1 xff0c 在序列尾部进行插入 删除是O 1 xff0c 但在序列的头部插入 删除的时间复杂度是O n xf
  • c语言(数组)

    交换算法 xff08 将最小值换到第一位 xff0c 最大值换到最后一位 xff09 include lt stdio h gt void main int o 61 0 int buf 10 接收用户输入的数组 for o lt 10 o
  • STM32外设串口资源用完了怎么办--------串口模拟解决问题(再也不用多个STM32或其它MCU)

    之前做项目的时候遇到了一个问题 xff0c 当把MCU本身的串口资源用完的时候 xff0c 却还需要使用多几个串口 xff0c 又不想使用几个MCU解决这个问题 那么模拟串口是解决这个问题的一种方法 下图是我对串口通信时序图的个人理解 xf
  • scanf函数中的格式字符串及注意事项

    scanf函数称为格式输入函数 xff0c 即按用户指定的格式从键盘上把数据输入到指定的变量之中 scanf函数的一般形式为 xff1a scanf 格式控制字符串 地址表列 xff1b 格式字符串的一般形式为 xff1a 输入数据宽度 长
  • 【C/C++】标准库, STL, Boost等的联系

    Backto C C 43 43 Index 标准库 最最开始 只有 C 语言 使用着使用着 一些常用的功能被写成了库 各种组织都是自己私有的库 后来为了方便统一使用和交流 就制定了标准 标准里的库 就是 C 标准库 后来 C 43 43
  • 数组与链表的优缺点和区别

    1 数组 xff1a 数组是将元素在内存中连续存放 xff0c 由于每个元素占用内存相同 xff0c 可以通过下标迅速访问数组中任何元素 但是如果要 在数组中增加一个元素 xff0c 需要移动大量元素 xff0c 在内存中空出一 个元素的空
  • 堆空间与栈空间的区别

    1 栈区 xff08 stack xff09 xff1a 又编译器自动分配释放 xff0c 存放函数的参数值 xff0c 局部变量的值等 xff0c 其操作方式类似于数据结构的 栈 2 堆区 xff08 heap xff09 xff1a 一
  • strtok函数及其实现

    头文件 xff1a include lt string h gt 定义函数 xff1a char strtok char s const char delim 函数说明 xff1a strtok 用来将字符串分割成一个个片段 参数s 指向欲
  • 服务器和客户机的信息函数以及读写函数

    1 服务器和客户机的信息函数 xff08 1 xff09 字节转换函数 在网络上面有着许多类型的机器 xff0c 这些机器在表示数据的字节顺序是不同的 xff0c 比如i386芯片是低字节在内存地址的低 端 xff0c 高字节在高端 xff
  • TCP协议的拥塞控制机制

    最初的TCP协议只有基于窗口的流控制 xff08 flow control xff09 机制而没有拥塞控制机制 流控制作为接受方管理发送方发送 数据的方式 xff0c 用来防止接受方可用的数据缓存空间的溢出 流控制是一种局部控制机制 xff
  • 构造函数为什么不能是虚函数

    1 从存储空间角度 xff0c 虚函数对应一个指向vtable虚函数表的指针 xff0c 这大家都知道 xff0c 可是这个指向vtable的指针其实是存储在对象的内存空间的 问题出来了 xff0c 如果构造函数是虚的 xff0c 就需要通
  • openmv学习五:OLED

    首先需要将SSD1306x py这个文件放到OPENMV中 代码 from machine import I2C Pin 从 machine 模块导入 I2C Pin 子模块 from ssd1306x import SSD1306 I2C
  • 自旋锁的实现及优化

    自旋锁也是一种互斥锁 xff0c 和mutex锁相比 xff0c 它的特点是不会阻塞 xff0c 如果申请不到锁 xff0c 就会不断地循环检测锁变量的状态 xff0c 也就是自旋 自旋锁的实现算法大多使用TAS算法或者CAS算法 TAS算