Linux线程互斥

在这里插入图片描述

1. 进程线程间的互斥相关背景概念

临界资源:多线程执行流都能看到并且能访问的资源。
临界区:每个线程内部,访问临界资源的代码

临界资源,可能会因为共同访问,可能会造成数据不一致问题。下面我们就以一个多线程抢票的方式来举例说明
在这里插入图片描述
我们让每个线程都回调这个函数去抢票,当票为0的时候就结束。
在这里插入图片描述
运行结果:
在这里插入图片描述
可能大家已经可以发现问题了。为什么会出现这样的情况呢?

这就是多线程并发访问临界资源,产生数据不一致问题。
首先,我们要理解if(tickets>0)和tickets- -这两个执行过程:
在这里插入图片描述
这是刚开始tickets在内存的状态,现在我们要执行减减,那么第一步:将tickets加载到CPU中
在这里插入图片描述
第二步:CPU进行减减操作
在这里插入图片描述
第三步:将算出的数据写回内存中
在这里插入图片描述
这是一个执行流执行完整情况下的过程。但是前面说过:1.在执行的过程中,任何时候,线程都可能被切换。2.CPU内的寄存器是被所有的执行流共享的,但是寄存器里面的数据是属于当前执行流的上下文数据。线程被切换的时候,线程需要保存上下文。线程被换回的时候,线程需要恢复上下文。
在这里插入图片描述
如果线程A在执行到第二步的时候,被切换线程B了,那么线程A就会把CPU中的上下文数据保存下来。
在这里插入图片描述
如果线程B抢了许多的票,将10000变成50。此时线程A再被调度,恢复它的上下文数据,再去执行第3步。
在这里插入图片描述
此时数据又变成了9999,可能就会造成票越来越多的情况。

如果我们要判断if(tickets>0):
在这里插入图片描述
也是一样的道理,如果此时只有一张票,线程A执行第一步完成后,切换成线程B,这样A和B都看到的都是1,这样在判断的时候,两个都是1,都会减减,就可能会出现负数票。

那么解决办法是什么呢
解决办法就是让每次执行不打扰,让它变成原子的。
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

那么下面也有了新的概念:
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

那么在Linux中,引入了互斥锁来解决这个问题,它在原生线程库里也实现了。

2. 创建锁和使用锁

在这里插入图片描述
这里定义的是一个全局锁,有锁的初始化和锁定释放。
在这里插入图片描述
在这里插入图片描述
知道怎么创建锁了,就需要使用它。
在这里插入图片描述
这里是加锁,尝试锁,取消锁。
在这里插入图片描述
我们可以在这里加锁和解锁吗?
在这里插入图片描述
我们可以看到运行结果:它只有一个线程来抢票了,其它线程抢不了了。

加锁的本质是让线程执行临界区代码串行化,只要对临界区加锁就行了,并且加锁的粒度约细越好
在这里插入图片描述
我们看这样加锁解锁行不行。
在这里插入图片描述
可以从运行结果看到:在这里不动了。因为在tickets为0的时候,break了。然后跳出循环,没有解锁。而线程1和线程2都在等这个锁释放,所以不退出,那么pthread_join就一直阻塞等待。
在这里插入图片描述
我们看一下这个运行结果:
在这里插入图片描述
大家可以看到都是线程3在抢票,其它线程都抢不到票,因为线程1和线程2在阻塞或者挂起,而线程3是加锁,CPU会认为加锁比唤醒更加高效,所以就让线程3一直抢票了。
在这里插入图片描述
运行情况如下:
在这里插入图片描述
锁保护的是临界区, 任何线程执行临界区代码访问临界资源,都必须先申请锁,前提是都必须先看到锁!那么这把锁,本身不就也是临界资源吗?那么谁来保护它呢
pthread_mutex_lock: 竞争和申请锁的过程,就是原子的。只存在申请成功和不成功

除了上面用函数来初始化,我们还有其它的初始化方式

用宏的方式:
在这里插入图片描述
我们可以用这个宏来初始化,使用 PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁。

用static修饰局部变量:
在这里插入图片描述
并且我们也可以把锁的这个变量以地址的形式传给线程,让每个线程可以看到。
在这里插入图片描述
我们传递的是锁的地址,但是我们线程的名字就打印不出来了。或者说我们想获取线程的其它属性,那么我们可以用这样的方法:
在这里插入图片描述
我们可以定义一个线程的结构体,里面含有线程的数据,比如说线程的名字和锁的地址。
在这里插入图片描述
我们可以创建这样的一个空间,然后对里面的数据进行设置。
在这里插入图片描述
这样的方法就能获取线程的数据。

那么在加锁的临界区里面,就没有线程切换了吗
答案是:还是可以切换的。因为线程在任意代码中都可以被切换

那么在切换的时候,有没有线程进入临界区
绝对不会有线程进入临界区。因为每个线程进入临界区都必须先申请锁。但当前的锁被切走了

所以,我们尽量不要在临界区内做耗时的事情。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学代码的咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值