卡码笔记
首页
计算机基础
C++
Java
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
首页
计算机基础
C++
Java
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
  • 基础与语法

  • 面向对象

  • STL 与容器

  • 内存管理

  • C++11 与现代 C++

  • 智能指针

  • 并发与 I/O

    • 互斥锁与自旋锁
      • 简要回答
      • 详细回答
      • 代码示例
      • 知识拓展
    • 说一下select,poll和epoll

# c++中自旋锁与互斥锁的性能对比及适用场景

面试时没有回答好问题是很正常的,被面试官质疑时,要有今天不会明天会的底气,面后做好复盘,不在同一道题目上跌倒第二次,今天就从 自旋锁与互斥锁的性能对 背起来

# 简要回答

自旋锁(Spinlock)通过忙等待实现同步,在用户态循环检查锁状态,适用于锁持有时间短的场景。

互斥锁(Mutex)通过系统调用实现阻塞,线程会进入睡眠状态,适用于锁持有时间较长的场景。

自旋锁避免上下文切换开销但浪费CPU周期,互斥锁节省CPU资源但引入切换开销。

# 详细回答

自旋锁特性:

实现机制:在用户空间忙等待,不断循环检测锁状态

CPU消耗:高,即使等待也在持续消耗CPU周期

上下文切换:无,线程始终保持运行状态

响应延迟:低,锁释放后能立即获取

实现复杂度:相对简单,通常基于原子操作

互斥锁特性:

实现机制:通过操作系统内核调度,线程阻塞并进入睡眠

CPU消耗:低,等待时不消耗CPU资源

上下文切换:有,涉及用户态到内核态切换

响应延迟:高,需要唤醒睡眠线程

实现复杂度:相对复杂,依赖操作系统支持

性能关键因素:

锁竞争强度:高竞争下自旋锁性能急剧下降

临界区大小:短临界区适合自旋锁,长临界区适合互斥锁

CPU核心数:多核系统更适合自旋锁

调度策略:实时系统可能偏好自旋锁

# 代码示例

// 自旋锁实现
#include <atomic>
#include <thread>
#include <iostream>

class Spinlock {
private:
    std::atomic_flag flag = ATOMIC_FLAG_INIT;

public:
    void lock() {
        // 自旋等待,直到成功获取锁
        while (flag.test_and_set(std::memory_order_acquire)) {
            // 可选的优化:在重度竞争时让出CPU
            // std::this_thread::yield();
        }
    }

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

// 使用示例
Spinlock spinlock;
int shared_data = 0;

void spinlock_worker(int id) {
    for (int i = 0; i < 1000; ++i) {
        spinlock.lock();
        shared_data++;  // 短临界区操作
        spinlock.unlock();
    }
    std::cout << "Thread " << id << " finished" << std::endl;
}


//------------------------------------------------------------------------


//互斥锁实现
#include <mutex>
#include <thread>
#include <iostream>

std::mutex mtx;
int shared_data = 0;

void mutex_worker(int id) {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        shared_data++;  // 短临界区操作
        // 模拟较长临界区操作
        // std::this_thread::sleep_for(std::chrono::microseconds(10));
    }
    std::cout << "Thread " << id << " finished" << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 知识拓展

  • 知识图解

image

  • 适用场景
  1. 自旋锁适用场景:

a.多核处理器系统 - 等待线程不会阻塞其他核心的工作

b.实时系统 - 需要确定性响应时间,避免调度延迟

c.内核开发 - 在中断处理程序中不能睡眠

d.极短临界区 - 锁持有时间小于两次上下文切换开销

e.低竞争场景 - 很少出现多个线程同时竞争锁

  1. 互斥锁适用场景:

a.单核处理器 - 自旋锁在单核上浪费CPU时间

b.长临界区操作 - 锁持有时间较长(>10-20μs)

c.高竞争场景 - 多个线程频繁竞争同一锁

d.功耗敏感应用 - 需要减少不必要的CPU消耗

e.用户空间程序 - 可以利用操作系统调度器

  • 面试官可能追问

Q1: 在单核CPU上为什么通常不建议使用自旋锁?

A1: 在单核CPU上,自旋锁会浪费宝贵的CPU时间。如果线程A持有锁,线程B自旋等待,但线程A无法运行来释放锁,因为CPU被线程B占用,这就造成了死锁般的状态。只有在可抢占内核或者配合主动让出CPU的情况下,单核系统才能有限使用自旋锁。

Q2: 自旋锁会不会引起活锁?

A2:会的。如果多个线程在同一时刻都尝试获取自旋锁,可能会出现"惊群效应",所有线程都在自旋但都无法获取锁。解决方法包括:

使用test-and-set等原子操作

引入随机退避机制

使用票据锁保证公平性

Q3: 如何确定自旋锁的自旋次数上限?

A3:可以通过以下因素综合考虑:

上下文切换的大致开销(通常1-10μs)

临界区的平均执行时间

CPU核心数量

系统负载情况 一般建议自旋次数为上下文切换开销的1.5-2倍,或者使用自适应策略动态调整。

Last Updated: 3/10/2026, 6:08:48 PM

← C++11中的智能指针线程安全性 说一下select,poll和epoll →

评论

验证登录状态...

侧边栏
夜间
卡码简历
代码随想录
卡码投递表🔥
2026群
添加客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记
鼓励/支持/赞赏Carl
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪