1. 背景概念
多线程中,存在一个全局变量,是被所有执行流共享的
根据历史经验,线程中大部分资源都会直接或者间接共享
只要存在共享,就可能存在被并发访问的问题
假设有一间教室被学校内的所有社团共享的,所以这个教室属于公共资源,
有可能当一个社团在这个教室举办活动时,别的社团也想占用这个教室
即 一个公共资源被并发访问了
为了保证访问时不能被别人去抢走,所以就把门窗都关上,直到访问完,才让别人进来
即 发生互斥
为了保证对应的共享资源的安全,用某种方式将共享资源保护起来,这部分共享资源称之为临界资源
访问临界资源执行的代码 称之为 临界区
2. 证明全局变量做修改时,在多线程并发访问会出问题
创建一个全局变量 tickets 作为票数,并创建4个线程,
分别调用自定义函数 thread_run 来对tickets进行–操作 ,直到tickets的值<0才结束
创建一个全局变量 tickets 作为票数,并创建4个线程,
分别调用自定义tickets变为负数 ,是不合理的
在我们设计中,若ticjets<0就会直接break退出,只有当tickets>0时才会打印出对应tickets的值
假设 tickets==1 ,此时有 a b c d 4个线程
当线程a 通过判断 进入 if语句中的 sleep中时 ,被上下文保护了
线程b 也执行判断 进入 if语句,继续向下执行完 tickets-- ,
此时的tickets的值为0,CPU就会再次执行还未执行完的线程a 的剩余步骤,tickets-- 即 0-1 =-1
3. 锁的使用
为了避免全局变量 出现负数的情况,所以引入 加锁 用于保证共享资源的安全
pthread_mutex_init
第一个参数 为 互斥锁,对该锁进行初始化,初始化该锁处于工作状态
第二个参数 为属性 一般设置为 nullptr
一般有两种初始化方案
第一种,锁为全局变量 ,直接用PTHREAD_MUTEX_INITIALIZER,对锁进行初始化
后面就不用 通过pthread_mutex_destroy 对其进行摧毁
第二种,若锁为局部变量,就必须调用pthread_init 进行初始化,用完后也必须调用 pthread_destroy 进行销毁
输入 man pthread_mutex_lock 加锁
输入 man pthread_mutex_unlock 解锁
具体操作实现
设置为全局锁
若锁为全局变量,可以选择在主函数中初始化锁 与销毁锁
设置为局部锁
4. 互斥锁细节问题
-
访问同一个临界资源的线程,都要进行加锁操作保护,而且必须加同一把锁
(每一个线程在访问临界资源之前都要先加锁) -
每一个线程访问临界区之前,得加锁,加锁本质是给临界区加锁
加锁粒度尽量要细一些 -
线程访问临界区的时候,需要先加锁 -> 所有线程都必须要先看到同一把锁 -> 锁本身就是公共资源
->锁如何保证自身安全? ->加锁和解锁本身就是原子的
(原子性:要么就不加锁,要加锁就加成功)
锁的申请是安全的,就可以保证锁保护的资源本身也是安全的 -
临界区可以是一行代码,也可以是一批代码
访问全局资源时,可能会存在多并发访问的问题
切换会有影响吗?
加锁在临界区内,加锁后,对临界区代码进行任意切换会不会影响数据出现安全方面的问题?
不会,我不在期间,其他人没有办法进入临界区,因为无法成功申请到锁,锁被我拿走了