Linux——什么是互斥与互斥锁

目录

一.前提:临界区 & 临界资源

二.什么是互斥

(一).互斥概念

(二).为什么需要互斥

三.互斥锁介绍

(一).互斥锁的概念

(二).互斥锁的使用

①系统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,总会有一个线程加锁成功。

图示如下:


如有错误,敬请斧正 

猜你喜欢

转载自blog.csdn.net/weixin_61857742/article/details/128762145
今日推荐