✧(≖ ◡ ≖✿
目录
原子 / 非原子
@原子操作对信号怎么处理?
非原子操作的矛盾
互斥锁
解决的问题
锁的本质
接口
定义锁
全局定义
局部定义
释放
加锁、解锁
@为什么加锁解锁都是原子的呢?
@加锁之后临界区内线程切换会怎么样?
多线程模拟并发
视频演示无锁将票抢到负数
视频演示有锁正常抢票
互斥锁的理解
执行流程
C++下的锁
互斥锁
RAII锁最常用的锁99%
原子 / 非原子
以 " a-- " 为例,探究原子与非原子。
原子操作的本质:给CPU发出指令对于“a”的修改不允许任何外部事件打断,也不允许被其他核心看到中间状态。
@原子操作对信号怎么处理?
以原子操作为优先,信号先被挂起。// kill -9 [pid]等硬核信号除外。
非原子操作的矛盾
以a--为例:
读取(Load):CPU 将
a的值(3)从主内存加载到它内部的寄存器(如eax)中。修改(Modify):CPU 在寄存器中执行
eax = eax - 1,此时eax的值变为 2。写入(Store):CPU 将寄存器中的新值(2)写回主内存中
a所在的内存地址。
在以上非原子操作中,若出现并发问题那么执行流就可能发生混乱而造成预料之外的结果。
解决方法——创建互斥锁来限制线程的并发
互斥锁
解决的问题
- 轻量级进程间信息过于同步而导致同时访问的问题。
- 多线程下临界区内非原子性操作带来的执行流内指令重置混乱的问题。
锁的本质
☆互斥锁lock()的本质是,将原先并行的轻量级进程改为串行。
接口
pthread_mutex_t
p:POSIX(portable Operating Systemd interface)可移植操作系统接口。
mutex:(mutual exclusion)互斥。
t:(type)类型。
定义锁
锁既需要定义还需要释放
全局定义
互斥锁在全局进行定义初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITLIZER;局部定义
必须使用pthread_mutex_init()
pthread_mutex_t mutex; int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);- *mutex:&锁。
- *attr:锁的属性,nullptr即可。
释放
int pthread_mutex_destroy(pthread_mutex_t* mutex);加锁、解锁
不论加锁还是解锁均是原子的
phtread_mutex_lock(pthread_mutex_t* mutex); phtread_mutex_unlock(pthread_mutex_t* mutex);加锁失败会阻塞挂起执行流。
@为什么加锁解锁都是原子的呢?
“上锁”的目的就是“保证锁内区域所有操作均是‘原子’的”,而只有持锁线程完毕才能“放下锁”——下一线程持锁进入锁区。若加锁不是原子的,那么也就导致了“持锁时并发混乱问题”。
@加锁之后临界区内线程切换会怎么样?
线程主导更换,但持锁线程不变。
多线程模拟并发
3个线程同时抢票
#include<unistd.h> #include<iostream> #include<pthread.h> static int ticket = 10000; void* routine(void* arg) { while (ticket) { //等待一会儿确保多个线程进来 usleep(500);//500ms printf("%d\n", --ticket); } return arg; } int main() { //3个新线程抢1000票 pthread_t pd1,pd2,pd3; pthread_create(&pd1, nullptr, routine, (void*)"pthread_1"); pthread_create(&pd2, nullptr, routine, (void*)"pthread_2"); pthread_create(&pd3, nullptr, routine, (void*)"pthread_3"); pthread_join(pd1, nullptr); pthread_join(pd2, nullptr); pthread_join(pd3, nullptr); return 0; }视频演示无锁将票抢到负数
无锁多线程抢票
发现循环一直进行不会终止。
原因分析🔍
while(ticket)仅当0时判断为假usleep(500)下足够线程进入循环内会将票数直接减为负数后再出循环判断。
在原子操作区加互斥锁即可
static int ticket = 500; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void* routine(void* arg) { while (ticket) { pthread_mutex_lock(&mutex); if(ticket == 0) { //先前线程已然将票抢完 这些线程就解锁并退出 pthread_mutex_unlock(&mutex); break; } //等待一会儿确保多个线程进来 usleep(1000);//500ms printf("%d\n", --ticket); if(ticket == 0) pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&mutex);// ticket>0解锁其他线程进来 } return arg; }视频演示有锁正常抢票
加锁抢票
互斥锁的理解
锁限制了“原子性”标志的掌握者,下图中%al是寄存器的一种。
执行流程
1.将寄存器内数据置零。
2.交换寄存器内数据与内存。
3.根据持锁(内存中的1标志),情况决定线程行为。
C++下的锁
互斥锁
最基础的独占锁,只有一个线程可以占用。
//定义 std::mutex mutex; //加锁、解锁 mutex.lock(); mutex.unlock();RAII锁最常用的锁99%
⚛️使用RAII原则“资源获取即初始化”,构造时自动加锁,出作用域自动解锁。(本质是封装以上述C接口的类,以调用相应的构造函数、析构函数)
std::lock_guard<std::mutex>(mtx);由于lock_guard是作用域相关所以常常需要{}来限定。
感谢支持,持续更新
欢迎关注