目录
一.前提:临界区 & 临界资源
二.什么是互斥
(一).互斥概念
(二).为什么需要互斥
三.互斥锁介绍
(一).互斥锁的概念
(二).互斥锁的使用
①系统API接口
②C++库
(三).互斥锁的底层原理
①加锁
②解锁
一.前提:临界区 & 临界资源
编写多线程程序时,多个线程可能需要执行同一个函数。如果该函数中有变量为这些线程共享,且可以改变,这样的变量可以称为临界资源。
相对应的改变这些资源的代码就叫做临界区。
示例如下代码:
int i = 0;//变量i为这些线程共享,即临界资源
void* func(void* arg){
...
//临界区起点
i = 3;
//临界区终点
...
}
int main(){
pthread_t tid[2];
pthread_create(&tid[0], nullptr, func, nullptr);
pthread_create(&tid[1], nullptr, func, nullptr);
pthread_join(tid[0], nullptr);
pthread_join(tid[1], nullptr);
return 0;
}
二.什么是互斥
(一).互斥概念
所谓互斥,其实就是在某一时刻只能有一个线程访问临界区,且完整的使用临界资源没有其他线程打扰,即原子性。
简单来说就是当前线程使用完临界资源后其他线程才能来使用。
(二).为什么需要互斥
如果多个线程同时访问临界资源,那么可能造成很严重的后果。
举个例子:
如下代码:
std::cout >> i >> std::endl;
i--;
假设此时i == 1,当有多个线程同时执行时,可能会发生意向不到的情况。
进行i--操作时,CPU其实分为三个步骤:
当线程A执行i--时,如果在②步骤执行完毕后被操作系统突然切换为线程B,那么i值不会写回内存,而是作为上下文数据被该线程携带:
当线程B执行i--时,①步骤从内存中读取的是1,之后②③步骤正常执行完毕,写回内存中,此时内存i值为0:
如果此时再将线程A切回,那么会直接执行③步骤,也就是说会将内存值写为0,而这就与程序不符了。
明明是被两个线程执行过,i应该是-1,而结果却是0!
因此,当多线程使用临界区时,要确保只能有一个线程访问该临界资源,且必须是原子性的使用资源。
换一种说法就是,如果没有互斥保护,那么多线程访问临界资源就是并发执行;当有互斥保护时,多线程问临界资源就是串行执行。
三.互斥锁介绍
(一).互斥锁的概念
互斥锁通俗来讲就是用来完成互斥行为的对象,锁住的范围一般就是临界区。
需要互斥操作的多线程共享一个互斥锁,当一个线程获得互斥锁后,其他线程会阻塞等待,直到当前线程归还互斥锁后,其他线程争抢互斥锁,谁获得了谁能进入临界区执行代码。
(二).互斥锁的使用
①系统API接口
头文件:<pthread.h>
定义:
pthread_mutex_t mtx;
初始化(两种方式):
pthread_mutex_init(&mtx, nullptr);//方式一
pthread_mutex_t mtx = PTHREAD_MUTEX_INITALIZER;//方式二
加锁与解锁:
lock与trylock的区别是,当多线程同时争抢互斥锁时,lock会让未抢到的线程阻塞等待,trylock不会阻塞等待。
pthread_mutex_lock(&mtx);
...//临界区
pthread_mutex_unlock(&mtx);
销毁:
pthread_mutex_destroy(&mtx);
②C++库
头文件:<mutex>
定义互斥锁对象:
std::mutex mtx;
加锁与解锁:
mtx.lock();//方式一,阻塞等待
mtx.try_lock();//方式二,非阻塞等待
...//临界区
mtx.unlock();
(三).互斥锁的底层原理
底层汇编:
①加锁
1.
这一步是将0值写入al寄存器中。
图示:
2.
将al寄存器值与mutex值(即互斥量)交换,无锁时mutex值是1。
图示:
3.
如果al值大于0,即该线程获得锁,那么返回。如果没有获取锁,线程会阻塞,阻塞结束后跳转重新执行加锁过程。
图示:
对于线程B而言,当被调用后使用CPU时,此时mutex中值为0,al先被赋值为0,与mutex交换后值依旧为0,因此,当执行第三步时,会判断为else的情况,即阻塞等待。当线程A执行完毕解锁后,再经过goto语句,重新执行加锁过程。
总之,所谓加锁,其实就是所有线程抢占一个互斥量(1),抢到的给al寄存器,加锁成功;没抢到的,al寄存器值为0,阻塞等待。
②解锁
相对于加锁,解锁很简单,就是当线程执行完临界区后,将mutex中的值置为1即可。
这样,当其他线程抢锁时,mutex值为1,总会有一个线程加锁成功。
图示如下:
如有错误,敬请斧正