当前位置: 首页 > article >正文

Linux初阶——线程(Part2):互斥同步问题

一、互斥锁

1、CPU 运算过程

执行完整个语句后,才会把数据写入内存;如果执行时被中断,那么数据和上下文就会保存到线程的 TCB,但数据并不会被写入内存。

1.1. 当 CPU 执行完整个语句时

CPU 最终执行完整个语句的过程

就用上图举个例子。 CPU 是如何执行 a-- 这个句代码的呢?

  • 第一步,CPU 将内存中变量 a 的值(100)拷贝到运算器中。
  • 第二步,在运算器中减一。
  • 第三步,把新的值(99)拷回变量 a 里。

1.2. 当 CPU 还未执行完这个语句,线程就被换出时

CPU 执行完减 1 操作,线程就被换出了

以上图为例,CPU 刚执行完减 1 操作,线程就被换出。遇到这种没执行完语句的情况,CPU 会把计算数据和该线程执行的上下文(即该线程执行到哪一行代码)拷到该线程的 TCB 里;然后再为下一个线程服务。

2、解决方案——互斥锁(mutex)

2.0. 什么是互斥问题

当多个线程并发执行,并访问同一个变量时,就会很容易发生变量的一致性问题。

如图所示,假如我想让变量 a 从 100 减到 0.

线程 1 在做完 a-- 操作后就被换出了:
 

线程 1 被换出

线程 2 正常执行完整个减 1 操作,并重复了很多次,直到减到 10,被换出,线程 1 进来了:

线程2被换出
线程 1 执行

而此时 a 这个全局变量又从 10 变成 100 了。

所以,这个变量从始至终都只能被一个执行流(线程)访问。

2.1. 互斥锁原理

下面这是线程申请锁和释放锁的汇编代码:

lock: // 申请锁
    movb $0, %al
    xchgb %al, mutex
    if (al 寄存器的内容 > 0) return 0; // 申请锁成功
    else 挂起等待;
    goto lock;

unlock: // 释放锁
    movb $1, mutex
    唤醒等待 Mutex 的线程;
    return 0;
lock 部分的汇编代码的前 2 行的执行过程

但是,如果当该线程执行完第一句后就被换出了,那么该线程会把 al 寄存器的值和上下文保存到 TCB;然后再被换出;然后第二个线程再进来从上次的上下文开始接着执行。所以,其实申请锁的本质就是看哪个线程拿到 1,拿到 1 的那个线程就申请到锁了。

2.2. 相关函数

2.2.1. pthread_mutex_t 类型

就是互斥锁的类型。

2.2.2. pthread_mutex_lock 函数

这个函数用于申请互斥锁。注意,这里只是把锁加上而已,并不会创建锁。 

2.2.3. pthread_mutex_unlock 函数

这个函数用于释放锁。注意,这里只是把锁解开了,锁还在。

2.2.4. 初始化全局的锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
2.2.5. 初始化局部变量的锁 
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数:
mutex:要初始化的互斥锁
attr:nullptr
 2.2.6. 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

这个函数是直接让锁消失。

注意:

  • 如果创建的锁是用宏创建的,就不能掉这个函数。 
  • 一定要在解开锁了之后才能调用这个函数。
  • 确保后面没有线程用到这个锁。

2.3. 临界区

处于 pthread_mutex_lock 函数和 pthread_mutex_unlock 函数之间的区域就是临界区。

2.4. 临界资源

多线程执行流共享的资源就叫做临界资源。不过,临界资源都是每次只能让一个一个线程访问的。但是当线程申请锁时,那么锁也是共享资源;所以申请锁和释放锁本来就被汇编语言设计成原子性的(即只用一句汇编实现)。

二、死锁

1、产生死锁的必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用。
  • 请求和保持条件:一个执行流因请求资源而阻塞时,它同时也持有已有的资源,且不释放。
  • 不剥夺条件:如果申请不到资源,只能等待,不能强行抢占。
  • 循环条件:执行流之间形成有环的等待资源的关系。

2、如何解决

  • 破坏上面的 4 条必要条件的其中一条。
  • 资源一次性分配。
  • 加锁顺序一致。
  • 避免未释放的场景。

三、同步问题

1、什么是同步

同步就是让线程按照一定顺序获取资源,而不是一窝蜂地去抢。举个例子,假如厕所是一个共享资源,一次只能一个人用。当有人进去后,就会有很多人在外面等。如果不同步的话,一旦厕所里的那个人出来,全部人就会一窝蜂地去抢厕所;而如果刚出来的那个人的抢占能力非常强,那么厕所就会一直被抢占能力最强的那个人占用,因为这个人可以出来后再抢。于是就会导致其他人长时间没得上厕所,进而引发其他人的饥饿问题。

因此,解决方法就是让所有人排队,一个个上厕所。从厕所出来的人无论抢占能力强还是弱,都要去队尾排队。这种让人们有序地使用厕所就是同步。而把线程看成人,那就就是线程的同步了。所以,总结来说,在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。

2、条件变量

2.1. 什么是条件变量

还记得前面厕所排队的例子吗?条件变量就是这个等待队列的头节点,当申请到资源时,线程就会从等待队列出来,然后访问共享资源;而如果线程没申请到资源时,该线程就会进入以条件变量为头节点的等待队列里。

2.2. 相关函数

2.2.0. pthread_cond_t 类型

该类型为条件变量类型。

2.2.1. 初始化全局的条件变量
pthread_cond_t global_cond = PTHREAD_COND_INITIALIZER;
2.2.1. 初始化局部变量的条件变量

参数介绍

  • cond:条件变量的地址。
  • attr: 条件变量的性质。

这个函数用于初始化条件变量。其实就是初始化等待队列的头节点。

2.2.2. pthread_cond_destroy 函数

参数介绍

  • cond:传条件变量的地址。 

注意:

  • 如果创建的条件变量是用宏创建的,就不能掉这个函数。 

这个函数用于释放条件变量。其实就是初始化等待队列的头节点。

2.2.3. pthread_cond_wait 函数

参数介绍

cond:传条件变量的地址。

mutex:传互斥锁的地址。 

此函数用于把线程加入等待队列。如果此时没申请到共享资源,该线程就会进入以条件变量为头节点的等待队列里。

2.2.4. pthread_cond_signal 函数

参数介绍

  • cond:条件变量地址。 

此函数用于唤醒线程。 即当线程申请到共享资源时,这个函数就能让一个线程的 TCB 退出以条件变量为头节点的等待队列。

2.2.5. pthread_cond_broadcast 函数

参数介绍

  • cond:条件变量地址。 

即当线程申请到共享资源时,这个函数就能让所有线程的 TCB 退出以条件变量为头节点的等待队列。 

四、应用:生产者消费者问题(cp 问题)

1、什么是生产者消费者问题

简单来说,前提就是生产者和消费者之间有一个共享资源(共享内存),生产者负责向共享内存放数据,消费者负责从共享内存里拿数据。然后我们通过一定的策略解决它们的互斥与同步问题。

2、3 种关系

2.1. 生产者与生产者

生产者与生产者之间是竞争关系,因此生产者与生产者之间是互斥关系。

2.2. 生产者与消费者

2.2.1. 互斥关系

因为要保证公共资源的安全性,因此要让生产者与消费者处于互斥关系,每次只能有一个线程访问共享资源。

2.2.2. 同步关系

因为共享内存里先有数据,消费者才可以拿数据。而生产者是负责向内存放数据的。因此先有生产者向共享内存放数据,才有消费者从共享内存拿数据,这是一种顺序访问共享内存;因此生产者与消费者也处于同步关系。

2.3. 消费者与消费者

消费者与消费者之间是竞争关系,因此生产者与生产者之间是互斥关系。


http://www.kler.cn/a/372899.html

相关文章:

  • macOS Sonoma 14.7.1 (23H222) Boot ISO 原版可引导镜像下载
  • 二十二、MySQL 8.0 主从复制原理分析与实战
  • vue打包项目通过docker部署nginx服务
  • 尽可能连续的基于挤压的表面模型制造
  • Docker部署教程:打造流畅的斗地主网页小游戏
  • 【数据集】MODIS地表温度数据(MOD11)
  • Nginx 配置基于主机名的 Web 服务器
  • SpringBoot接收LocalDateTime参数
  • c++中的指针相关
  • [Linux关键词]unmask,mv,dev/pts,stdin stdout stderr,echo
  • 使用原生HTML和css制作一个箭头步骤条
  • 【Nas】X-DOC:Mac mini Docker部署小雅Alist
  • Vue v-on
  • Android 在github网站下载项目:各种很慢怎么办?比如gradle下载慢;访问github慢;依赖下载慢
  • c++中的结构体
  • 深度了解flink(七) JobManager(1) 组件启动流程分析
  • 【HarmonyOS】鸿蒙应用低功耗蓝牙BLE的使用心得 (一)
  • 四款国内外远程桌面软件横测:ToDesk、向日葵、TeamViewer、AnyDesk
  • go-logger v0.27.0 - 并发性能为官方库 10 倍
  • uv: 一个统一的Python包管理工具
  • 游戏引擎中的颜色科学
  • 使用docx4j+docx4j-ImportXHTML实现将html转成word
  • PHP合成图片,生成海报图,poster-editor使用说明
  • 华为云Stack名词解释
  • 嵌入式硬件电子电路设计(一)开关电源Buck电路
  • es安装拼音分词后Kibana出现内存错误