我能说不知道吗,我只能说知道
面试官:伸手一摆,说说你理解
大部分项目都使用互斥锁,遇到阻塞时不会占用CPU,狗都不使用...
各位老师好,我是小义,喜欢研究一些没有用的底层技术知识。
本文是大厂面试拆解第6篇文章,主要详细描述:
希望在无锁编程方面对你有帮助。
时间:2025年4月28日 岗位:数据库开发工程师 形式:线下
一面
基础面试:
项目面试:
这个题目之前我准备过,参考了以下书籍:
当初学习时,快速翻页,囫囵吞枣,现在很多细节都记不清楚了。 不过现在不是后悔的时候,需要快速调整心态。
回答如下:
自旋锁常用于高并发场景下,相比互斥锁:
点评:这个题目有陷阱,"谈谈xxx"不是发散题目, 而是需要聚焦使用场景和采用的技术。看到资料都是10年前的, 而且学习过程中没有完全理解,对10年后的发展了解不够。
多个人竞争一个自旋锁时存在以下问题:
自旋锁发展历史
这个技术适用的场景
现代高性能并发库和语言运行时(如Java并发包中的AbstractQueuedSynchronizer
)也普遍采用MCS或类似的链式队列锁实现,以提升多核环境下的锁性能。
应用示例:
思考要点:
我的理解如下:
自旋锁是一种低延时场景下的多线程同步机制,采用自旋等待(Busy‑Waiting)方式。
技术要点:
std::mutex
等基于futex的互斥量不同,自旋锁完全通过原子指令实现,无需进入内核态https://compiler-explorer.com/z/8v9E5vjYv
汇编解释
.LBB0_1:
cmp dword ptr [rbp - 4], 10000000 ; (1) 比较 i 与 10000000
jge .LBB0_4 ; (2) 如果 i >= 10000000 跳出
lea rdi, [rip + sum1] ; (3) 取 sum1 地址,放入 rdi 作为 this
call std::__atomic_base<int>::operator++() ; (4) 调用原子自增
mov eax, dword ptr [rip + sum2] ; (5) 读 sum2
add eax, 1 ; (6) eax = eax + 1
mov dword ptr [rip + sum2], eax ; (7) 写回 sum2
mov eax, dword ptr [rbp - 4] ; (8) 读 i
add eax, 1 ; (9) i = i + 1
mov dword ptr [rbp - 4], eax ; (10) 写回 i
jmp .LBB0_1 ; (11) 跳回循环开头
.LBB0_4:
; 循环结束,返回/退出
call std::__atomic_base<int>::operator++()
会展开为带 `LOCK` 前缀的读-改-写指令,
例如 `lock inc dword ptr [rip + sum1]`,
确保在多核环境下对该地址的操作是原子的
https://github.com/torvalds/linux/blob/master/arch/sh/include/asm/spinlock-cas.h
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
while (!__sl_cas(&lock->lock, 1, 0));
}
static inline unsigned __sl_cas(volatile unsigned *p, unsigned old, unsigned new)
{
__asm__ __volatile__("cas.l %1,%0,@r0"
: "+r"(new)
: "r"(old), "z"(p)
: "t", "memory" );
return new;
}
Folly 是一个由Facebook 开源的C++ 库,旨在为大规模服务器开发提供高效的工具和组件
folly::MicroSpinLock` 是在 Folly 中为超细粒度锁场景设计的极小自旋锁,
detail::Sleeper
进行"退避"自旋,从而避免任何系统调用或内核阻塞,保证极低的延迟和良好的跨平台可移植性。
https://github.com/facebook/folly/blob/main/folly/SpinLock.h
SpinLock--->folly::MicroSpinLock
/*
* folly::MicroSpinLock
* ====================
* A really, *really* small spinlock for fine-grained locking of lots
* of teeny-tiny data.
*
* Designed to be POD (Plain Old Data) so it can be
* packed into other structures without extra overhead.
*
* 特性:
* 1. 极小自旋锁,适用于超细粒度场景
* 2. POD 类型,可零初始化,相当于调用 init()
* 3. 用户态实现,无系统调用开销
* 4. 支持动态检测工具插桩(ThreadSanitizer 等)
*/
struct MicroSpinLock {
// FREE 表示锁可用,LOCKED 表示锁已被持有
enum { FREE = 0, LOCKED = 1 };
//
// 用一个字节存储锁状态,避免使用 std::atomic<>,保持 POD 特性
//为什么用uint8_t表示 std::atomic<>?
//这个和序列化有什么关系?怎么实现的
typedefunsignedchar uint8_t;
uint8_t lock_; //locked?
/**
* 尝试获取锁,非阻塞
* @return true: 获得锁;false: 未获得
*/
bool try_lock() noexcept {
// 调用 xchg 将新值置为 LOCKED,返回旧值
bool ret = (xchg(LOCKED) == FREE);
// 插桩:记录尝试获取锁的结果,便于动态分析
annotate_rwlock_try_acquired(
this, annotate_rwlock_level::wrlock, ret, __FILE__, __LINE__);
return ret;
}
/**
* 获取锁,阻塞自旋
* 在持锁竞争时,会进行退避等待以降低总线抖动
*/
void lock() noexcept {
detail::Sleeper sleeper;
// 自旋尝试交换,如果旧值不为 FREE,表示竞争失败
while (xchg(LOCKED) != FREE) {
// 进入退避等待循环
do {
sleeper.wait(); // 调用 pause/yield 或指数退避
} while (payload()->load(std::memory_order_relaxed) == LOCKED);
}
// 加锁成功,断言当前状态为 LOCKED
assert(payload()->load() == LOCKED);
// 插桩:记录获取锁的事件
annotate_rwlock_acquired(
this, annotate_rwlock_level::wrlock, __FILE__, __LINE__);
}
/**
* 释放锁
* 将状态设为 FREE,并使用 release 语义保证可见性
*/
void unlock() noexcept {
// 断言当前持锁状态
assert(payload()->load() == LOCKED);
// 释放锁 write
payload()->store(FREE, std::memory_order_release);
}
private:
/**
* 内部:获取指向 lock_ 字段的原子指针
*/
std::atomic<uint8_t>* payload() noexcept {
// reinterpret_cast 到 std::atomic<uint8_t>* 类型
returnreinterpret_cast<std::atomic<uint8_t>*>(&this->lock_);
}
/**
* 原子交换操作,将 lock_ 原子地置为 newVal,返回旧值
*/
uint8_t xchg(uint8_t newVal) noexcept {
returnstd::atomic_exchange_explicit(
payload(), newVal, std::memory_order_acq_rel);
} //A read-modify-write operation with this memory order is both an _acquire operation_ and a _release operation_
};
// 编译时断言:类型必须满足标准布局且平凡类型,以保证 POD 特性
static_assert(
std::is_standard_layout<MicroSpinLock>::value &&
std::is_trivial<MicroSpinLock>::value,
"MicroSpinLock must be kept a POD type.");
原理:
参考:https://mfukar.github.io/2017/09/08/ticketspinlock.htm
now_serving next_ticket
| |
V V
... 01234567012 ...
\___________/
8 threads
holding one ticket each
struct TicketSpinLock {
/**
* Attempt to grab the lock:
* 1. Get a ticket number
* 2. Wait for it
*/
void enter() {
/* We don't care about a specific ordering with other threads,
* as long as the increment of the `next_ticket` counter happens atomically.
* Therefore, std::memory_order_relaxed.
*/
constauto ticket = next_ticket.fetch_add(1, std::memory_order_relaxed);
while (now_serving.load(std::memory_order_acquire) != ticket) {
spin_wait();
}
}
/**
* Since we're in the critical section, no one can modify `now_serving`
* but this thread. We just want the update to be atomic. Therefore we can use
* a simple store instead of `now_serving.fetch_add()`:
*/
void leave() {
constauto successor = now_serving.load(std::memory_order_relaxed) + 1;
now_serving.store(successor, std::memory_order_release);
}
c++ 内存模型
顺序 | 语义保证 | 开销 |
---|---|---|
memory_order_relaxed | 仅保证原子性,不保证与其他操作的顺序或可见性 | 最低 |
memory_order_acquire | 保证此操作后续的读写不会在指令序上移到此操作之前 | 较低 |
memory_order_release | 保证此操作前的读写不会在指令序上移到此操作之后 | 较低 |
memory_order_acq_rel | 同时具备 acquire 和 release 语义,对应读写双向屏障 | 中等 |
memory_order_seq_cst | 全局顺序一致性;在所有线程中可见操作顺序完全一致 | 最高 |
参考
以解决在锁的争用比较激烈的场景下,cache line无谓刷新的问题,
其主要思想是让每个微调器在其各自的 per-CPU 变量上旋转,从而避免不同 CPU 之间不断的缓存行跳动。
当所有 CPU 都自旋等待同一个锁变量时,就会发生缓存行跳动,这会导致它们重复读取此变量。当一个 CPU 解锁时,此变量会被修改,从而使所有其他 CPU 的缓存行无效,然后这些 CPU 必须重新读取该变量。
这会导致性能开销。MCS 锁通过让每个 CPU 在其各自的专用变量上旋转来缓解这个问题,从而避免了对单个锁变量的争用。
先来看一下有3个以上的CPU持有或试图获取spinlock时,等待队列的全貌
Linux 版本 | 时间 | 主要进展 | 自旋锁相关说明 |
---|---|---|---|
2.0 | 1996 | 支持 SMP,BKL 引入 | 全局大内核锁,串行化所有内核操作,无需细粒度锁 |
2.2 | 1999 | 引入基础自旋锁机制 | 初步在某些子系统中使用 spinlock_t,开始去BKL化 |
2.4 | 2001 | 更广泛使用自旋锁 | 自旋锁与 cli/sti(关中断)结合使用,锁粒度更细 |
2.6 | 2003 | 高度并发与抢占内核 | 引入 spin_lock_irqsave() 等多种锁版本,应对复杂场景 |
3.x | 2011 | BKL 基本移除 | 通过细粒度自旋锁和 mutex 替代 BKL |
4.2 | 2015 | 引入 qspinlock | MCS 队列自旋锁替代原始的 ticket lock,提高扩展性 |
5.x | 2018+ | NUMA、多核优化 | 更复杂的锁策略:退避、统计锁竞争、结合 RCU 等 |
锁竞争激烈、CPU 浪费严重
NUMA 架构下锁竞争代价高
参考:https://coolshell.cn/articles/8239.htm
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有