独立于架构的自旋等待循环

独立于架构的自旋等待循环

由 Jon Kennedy. (Intel)IDZSupport K. 发布于 2018 年 4 月 26 日

 

翻译简体中文繁体中文英语法语德语意大利语葡萄牙语俄语西班牙语土耳其语

翻译

为了充分利用当今多核处理器的功能,游戏开发人员正使用更高级的任务处理系统使工作分散到线程池的多个线程之中。随着线程数量的增加,诸如任务队列锁和其他共享资源结构上的线程之间发生争用的可能性也会增加。有多种解决办法,但是自旋等待循环是常见的结构。

1while (!acquire_lock())
2{
3    // Spin for a specific number of iterations
4    for (int i = 0; i < max_spin_count; i++)
5    {
6        // Pause intrinsic
7        _mm_pause();
8    }
9}

本文使用了 The _mm_pause 指令,因为它可以提示处理器调用线程位于自选等待循环中。这将暂停下一条指令的执行,不再需要处理器,也不会使用部分管线,因此降低了能耗。

_mm_pause 指令在多数前几代英特尔平台上拥有相似的延迟。由于存在这种历史一致性,许多开发人员在调整自旋循环时会考虑到这一点。但是,从第六代智能英特尔® 酷睿™ i 处理器产品家族开始,pause 指令的延迟增加了多个数量级,旨在为许多场景提供更好的节能机会。

由于延迟发生了变化,以上固定数量的自旋循环将消耗比之前高一个数量级的周期,这将对您的应用性能造成不利影响。为了避免未来的指令架构变动引起任何问题,对所有自旋等待循环进行检查,以确保未使用固定数量的 pause 指令实施它们。可以对上述自旋等待循环进行以下适当修改:

01while (!acquire_lock())
02{
03    // __rdtsc intrinsic is used to read the time stamp counter
04    // This allows the loop to run for a fixed number of cycles
05    uint64_t prev = __rdtsc();
06    do
07    {
08        // Pause intrinsic
09 
10        _mm_pause();
11    }
12    while ((__rdtsc() - prev) < max_spin_time)
13    }

尽管上述自旋等待循环非常简单,软件开发人员一般会使用更高级的自旋循环(支持指数退避等),但是它展示了如何使软件变得更强大,以应对未来指令延迟的架构变动。

 

请参阅以下文章,进一步了解 _mm_pause 指令与自旋等待循环:有益于功耗与性能的睡眠循环

如欲获取关于英特尔® 架构编程的更多信息,请参阅以下软件开发手册:《64 位和 32 位英特尔®架构软件开发人员手册》

<think>好的,我需要回答用户关于操作系统中常见内核锁类型及实现方式的问题。首先,我应该回顾已有的引用内容,特别是引用[2]提到的原子操作和内核锁的相关信息。用户想了解内核锁的类型和用途,以及它们的实现方式。 首先,我需要明确内核锁的基本概念。内核锁用于保护共享资源,防止多线程或多处理器环境下的竞态条件。根据引用[2],原子操作和无锁编程是解决锁机制问题的方法,但内核锁本身有多种类型,比如自旋锁、信号量、互斥锁等。 接下来,我需要整理常见的内核锁类型。根据所学知识,自旋锁(spinlock)在等待时会不断检查锁状态,适用于短期等待的场景;信号量(semaphore)可以允许一定数量的线程访问资源,而互斥锁(mutex)是二进制的,同一时间只允许一个线程访问。此外还有读写锁(rwlock)、顺序锁(seqlock)等。 然后,考虑实现方式。自旋锁通常通过原子操作和忙等待实现,例如使用test-and-set指令。信号量可能使用等待队列和调度机制,让线程休眠而不是忙等待。互斥锁的实现可能结合自旋锁和信号量,具体取决于操作系统。 需要注意引用[2]提到的无锁编程,但用户的问题是锁的类型,所以应集中在有锁机制上。同时,引用[3]提到作者擅长并发性能架构设计,可能需要强调锁的选择对性能的影响,比如自旋锁适用于多核短期等待,而信号量适合可能长时间阻塞的情况。 需要确保回答结构清晰,分点列出每种锁的类型、用途和实现方式。同时,根据用户要求,生成相关问题。例如,锁的性能比较、适用场景、死锁预防等。 最后,检查是否符合格式要求:使用中文回答,数学公式用$...$,独立公式用$$...$$,引用标识正确添加。但用户的问题不涉及数学公式,可能不需要使用。不过如果有涉及到同步原语的实现细节,可能需要用代码块或算法描述,但根据示例,可能更倾向于用自然语言解释。</think>### 操作系统中常见的内核锁类型及实现方式 操作系统中内核锁的主要作用是**保护共享资源在多线程或多核环境下的原子访问**,防止数据竞争和不一致性。以下是常见的锁类型及其实现方式: --- #### 1. **自旋锁(Spinlock)** - **用途**:适用于**短期等待**的场景,例如中断处理或临界区执行时间极短的情况。线程会通过“忙等待”(不断循环检查锁状态)来获取锁。 - **实现方式**: - 基于**原子操作**(如 `test-and-set` 或 `compare-and-swap`),通过硬件指令保证操作的原子性[^2]。 - 示例代码(伪逻辑): ```c while (atomic_test_and_set(&lock) == BUSY); // 忙等待直到获取锁 // 临界区操作 atomic_clear(&lock); // 释放锁 ``` - 在 Linux 内核中,自旋锁的实现还会根据是否在中断上下文中使用,分为普通自旋锁和“下半部”(BH)自旋锁。 --- #### 2. **信号量(Semaphore)** - **用途**:用于**长期等待**或需要调度的场景(如 I/O 操作)。线程若未获取锁会进入睡眠状态,由内核调度其他线程运行。 - **实现方式**: - 维护一个**计数器**和**等待队列**。当计数器为 0 时,线程被加入等待队列并进入阻塞状态。 - 通过 `down()`(获取锁)和 `up()`(释放锁)操作修改计数器,使用内核调度机制管理阻塞线程。 - 信号量分为**二进制信号量**(类似互斥锁)和**计数信号量**(允许多个线程访问资源)。 --- #### 3. **互斥锁(Mutex)** - **用途**:与信号量类似,但更轻量,用于**互斥访问单一资源**。 - **实现方式**: - 通常结合自旋锁和信号量。例如,在 Linux 中,互斥锁可能先尝试自旋等待,若失败则切换为睡眠等待。 - 支持**优先级继承**(防止优先级反转问题),确保高优先级线程优先获取锁。 --- #### 4. **读写锁(Read-Write Lock)** - **用途**:区分读/写操作,允许多个读线程同时访问,但写线程独占资源。 - **实现方式**: - 使用两个计数器分别记录读/写线程数量。 - 写锁获取时需等待所有读锁释放,读锁获取时需检查是否有写锁被持有。 --- #### 5. **顺序锁(Seqlock)** - **用途**:适用于**读多写少**的场景,如系统时间更新。读操作不阻塞,但可能重试。 - **实现方式**: - 通过一个**序列号**标识数据版本。写操作会递增序列号,读操作前后检查序列号是否一致,若不一致则重试。 --- #### 6. **RCU(Read-Copy-Update)** - **用途**:无锁读操作,适用于高频读、低频写的场景(如路由表)。 - **实现方式**: - 写操作通过创建数据副本完成更新,旧数据在所有读操作结束后被回收。 - 依赖内存屏障和延迟释放机制实现同步。 --- ### 性能与适用场景对比 | 锁类型 | 适用场景 | 性能特点 | |----------|-------------------------|------------------------------| | 自旋锁 | 短期临界区、多核环境 | 低延迟,但忙等待消耗 CPU | | 信号量 | 长期阻塞(如 I/O) | 线程睡眠,节省 CPU | | 读写锁 | 读多写少 | 读并发高,写操作互斥 | | RCU | 读极多、写极少 | 读无锁,写需同步 | --- ### 实现中的关键问题 1. **死锁预防**:通过锁顺序协议、超时机制或死锁检测算法避免。 2. **锁粒度**:粗粒度锁简单但并发性低,细粒度锁复杂但并发性高。 3. **无锁编程**:如原子操作(`atomic_add`)或 CAS(Compare-and-Swap),适用于简单操作。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值