Linux环境下 解决线程共享资源冲突 —— 互斥锁(代码实现及底层原理)

为了保护这些共享资源在被使用的时候,不会受到其他线程的影响,因此我们要为临界区加锁。c++11已经支持了互斥锁mutex,mutex本质上还是封装了pthread库中原生的互斥锁,下面要介绍的是pthread库提供的原生互斥锁,pthread库的互斥锁更底层,更接近系统级调用。


目录

一、互斥锁相关函数

1、创建锁

2、初始化锁 pthread_mutex_init

3、销毁锁 pthread_mutex_destroy

4、上锁 pthread_mutex_lock、解锁 pthread_mutex_unlock

二、互斥锁解决资源冲突

三、互斥锁的底层原理

1、锁创建成功并初始化以后

2、加锁和解锁的简单理解

3、互斥锁总结


一、互斥锁相关函数

pthread给我们提供了“互斥锁”这种数据类型 —— pthread_mutex_t,除此之外还有初始化锁、加锁、解锁、销毁锁等相关的函数。

1、创建锁

(1) 方式一:仅仅只是创建锁

这步其实就是在声明一个锁的变量

pthread_mutex_t mtx;    //创建一把锁

(2) 方式二:创建锁+初始化锁一步到位

 一般在main函数中创建然后把锁的地址传递给子线程,或者直接在最前面加一个static,这样的话就不是在主线程的栈上开辟,而是在静态区开辟,无需传递锁的地址,因为所有的线程都能访问到静态区。

2、初始化锁 pthread_mutex_init

创建出锁以后,需要给这把锁初始化,你可以理解为设置锁的密码。

第一个参数指的是要给哪个锁初始化;第二个参数是设置锁的属性,这个老样子,我们不懂,交由pthread库决定,一般设置为NULL

返回值:成功返回0,失败返回一个错误码

3、销毁锁 pthread_mutex_destroy

现在我们不需要这个锁了,我们要把这个锁销毁,注意!销毁锁 ≠ 解锁

参数就是你要销毁哪把锁;返回值:成功返回0,失败返回一个错误码

4、上锁 pthread_mutex_lock、解锁 pthread_mutex_unlock

这两个函数的参数都一样,都指的是要使用哪把锁;成功返回0,失败返回错误码

这两个函数一般需要搭配使用

pthread_mutex_t mtx;
pthread_mutex_lock(&mtx);    //加锁

//...受到锁保护的临界区

pthread_mutex_lock(&mtx);    //解锁

二、互斥锁解决资源冲突

现在我们来解决之前的抢票问题

=======================主线程 =======================

=======================子线程 =======================

测试结果如下,此时留意一下线程ID,你会发现最后900张票都是一个线程抢的,这是因为没有了其他线程的干扰,一个线程能完整的走完自己的时间片,一个线程的时间片虽然短,但是能抢一批

 三、互斥锁的底层原理

你是否注意到,我们在上述过程中只用了一把锁,要达到互斥的目的,那就要求所有的线程都能看到这把锁,也就是说 互斥锁 也是临界资源,既然所有的线程都能申请锁,那不就乱套了吗?

答案是并不会,并不是所有的线程都能申请成功!

1、锁创建成功并初始化以后

当我们把锁创建出来,并初始化以后,内存中就会有一个变量叫做mutex,此时mutex = 1,先这么理解,谁拿到这个 1,就代表谁拥有锁!此时很显然,内存持有锁

2、加锁和解锁的简单理解

(1) 加锁

======================线程A申请锁======================

假设一个线程A申请锁的时候,实际上站在汇编的角度执行的就是下面的内容

第一步,movb 在汇编中表示赋值,后者赋值给前者,%al 代表CPU里的寄存器,$0 代表0。当线程A申请锁的时候,先把 0 放到寄存器中 (初始化寄存器)。

第二步,xchgb 在汇编中表示交换,前者的值和后者交换,%al表示CPU里的寄存器,mutex表示上面创建的变量。将寄存器中%al的值和内存中mutex的值交换。

第三步,此时是线程A被调度,在A的时间片内,%al寄存器是A私有的,里面的数据也是,这就表明线程A 持有锁,此时寄存器的值大于0,可以访问临界区。这是申请成功的情况。

======================线程B申请锁======================

线程A 的时间片到了,但是A没有解锁,此时线程A会把 %al 里的值 也就是 1 放到自己的PCB里,排到队尾。然后线程B来申请锁了。

第一步,此时线程B被调度,%al是线程B私有的,依然是先把 %al 的值赋值为 0,然后和 内存的mutex交换。但是mutex = 0说明此时锁不在内存手里,%al交换以后依然为0

第二步,寄存器%al的值为0,if语句条件不通过,此时线程B就会被挂起,无法访问临界区。这是申请失败的情况。

 (2) 解锁

现在线程A来解锁了,解锁对应的汇编内容如下

 此时把mutex的值赋值为1,也就是把锁还给内存中的mutex变量,然后环形等待mutex的线程。比如唤醒线程B,然后线程B就会去执行上面申请锁的汇编语句。

3、互斥锁总结

使用互斥锁的代码可以简化为下面这个模型

如果线程在向下执行的过程中,申请了锁,如果成功,那么你就可以访问临界区;但是!如果线程申请失败了,那么该线程会被挂起,不会跳过加锁的区域,然后向下运行。

如果线程在向下执行的过程中,因为if语句条件不通过,导致没有申请锁这个过程,那么该线程就不会被挂起

==》只有申请了锁,而且申请失败了,那么这个线程就会被挂起,直到上一个线程解锁以后才会被唤醒

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值