锁的本质
锁的本质:操作的序列化、队列化
虽然不同的语言可能会提供不同的锁接口,但是底层调用的都是操作系统的提供的锁。
不同的高级语言只是在操作系统的锁机制基础上进行了些封装而已
硬件
原子操作是指不可被中断的一个或者一组操作。
硬件提供原子指令,支持基本类型。
总线锁
在cpu芯片上有一个HLOCK Pin,可以通过发送指令来操作,将#HLOCK Pin电位拉低,并持续到这条指令执行完毕,从而将总线锁住,这样同一总线上的其他CPU就不能通过总线来访问内存了,保证了这条指令在多处理器环境中的原子性。
总线锁代价高。
缓存锁
某个CPU对缓存数据进行更改时,会通知缓存了该数据的该数据的CPU抛弃缓存的数据或者从内存重新读取。
缓存锁代价低,但是复杂,需要额外的代价保证缓存一致性。
MESI协议是经典的缓存一致性协议。
两种不能使用缓存锁的情况
- 第一种情况是操作的数据不能被缓存在处理器内部,或者操作的数据跨多个缓存行(cache line)时,则处理器会调用总线锁定。
- 第二种情况是处理器不支持缓存锁定,对于Intel 486和Pentium处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。
伪共享
如果多个核的线程在操作同一个缓存行中的不同变量数据,那么就会出现频繁的缓存失效,即使在代码层面看这两个线程操作的数据之间完全没有关系。这种不合理的资源竞争情况就是伪共享(False Sharing)。
linux下查看Cache Line大小,一般为64Byte
cat /proc/cpuinfo | grep "cache_alignment"
避免伪共享。对于多线程共享变量才需要加对齐防止伪共享。缓存行填充,将结构体的大小前后凑齐到64Byte。
缓存行对齐。如果两个数据,访问其中一个意味着大概率也会访问另一个,那么可以将它们放到同一个 cacheline。
操作系统
进程执行时需要资源,操作系统做了归一化处理,用锁来代替资源。操作系统就不用关心进程的细节。
操作系统会维护一个“锁”的列表;找到这个锁的对应项,读它的相关信息,再找到申请它的进程队列。
锁的分类
- “自旋锁”是一种“申请不到也不知会操作系统”的锁。
- 其它锁都是“申请不到就通知操作系统:资源不足,我没法干活了,申请休息”。
资源的分类
- 同时只允许一个访问,无论读写。“互斥锁”。
- 同时只允许一个修改、但可以允许许多个读取(读取时不得写入)。“读写锁”。
通过不同的锁,进程就可以配合操作系统,做到“既不浪费CPU时间、又尽量把各种资源利用到极致”了。
用户态
悲观锁
前面提到的互斥锁、自旋锁、读写锁,都是属于悲观锁。
悲观锁认为修改资源冲突的概率较高,访问资源前,先要加锁。
并发场景下性能显著下降。
乐观锁
认为冲突的概率较低,先修改资源,再回写资源。回写资源时,判断是否冲突。没有冲突,修改成功。存在冲突,放弃操作或者重试。
全程没有涉及到锁,也叫作无锁编程。适用于多读少写的场景。
可以通过版本号机制和CAS算法实现。
乐观锁缺点:
- ABA问题。增加预期标志。
- CAS(Compare and Swap)循环时间长开销大。
- 只能保证一个共享变量的原子操作。
语言层C++
atomic
C++11 提供了一个原子类型 std::atomic
std::atomic
std::atomic
#include <iostream>
#include <utility>
#include <atomic>
struct A { int a[100]; };
struct B { int x, y; };
int main(){
std::cout << std::boolalpha
<< "std::atomic<A> is lock free? "
<< std::atomic<A>{}.is_lock_free() << '\n'
<< "std::atomic<B> is lock free? "
<< std::atomic<B>{}.is_lock_free() << '\n';
}
Possible output:
std::atomic<A> is lock free? false
std::atomic<B> is lock free? true
如果is_lock_free=true,底层会调用cpu提供的原子指令。否则,调用mutex。
mutex
标准的互斥锁,满足三个1:一个线程,锁一次,解锁一次。
recursive_mutex
可重入互斥锁。满足1NN:一个线程,锁N次,解锁N次。
lock_guard
简单的RAII封装,在构造函数中进行加锁,析构函数中进行解锁
unique_lock
std::lock_guard的功能超集。
性能和内存开销都比std::lock_guard大得多。需要有选择地使用。
condition_variable
条件变量,这个不是锁,是一种线程间的通讯机制,并且几乎总是和互斥量一起使用的。
等待-通知。
std::mutex m;
std::condition_variable cond;
bool data_ready=false;
void process_data();
void foo(){
std::unique_lock<std::mutex> lk(m);
cond.wait(lk,[]{return data_ready;});//return only if data_ready is true
process_data();
}
void set_data_ready(){
std::lock_guard<std::mutex> lk(m);
data_ready=true;
cond.notify_one();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了