Linux线程互斥

Linux线程互斥_第1张图片

文章目录

  • 1. 进程线程间的互斥相关背景概念
  • 2. 创建锁和使用锁

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

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

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

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

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

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

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

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

2. 创建锁和使用锁

Linux线程互斥_第13张图片
这里定义的是一个全局锁,有锁的初始化和锁定释放。
Linux线程互斥_第14张图片
Linux线程互斥_第15张图片
知道怎么创建锁了,就需要使用它。
Linux线程互斥_第16张图片
这里是加锁,尝试锁,取消锁。
Linux线程互斥_第17张图片
我们可以在这里加锁和解锁吗?
Linux线程互斥_第18张图片
我们可以看到运行结果:它只有一个线程来抢票了,其它线程抢不了了。

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

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

用宏的方式:
Linux线程互斥_第25张图片
我们可以用这个宏来初始化,使用 PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁。

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

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

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

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

你可能感兴趣的:(Linux,Linux,线程,互斥锁)