Skip to content

5.3 多线程冲突了怎么办?

竞争与协作

多个线程如果竞争共享资源,如果不采取有效的措施,则会造成共享数据的混乱。

比如循环打印数字,出现数据不一致问题。

竞争条件(race condition::多线程相互竞争操作共享变量时,在执行过程中发生了上下文切换,得到了错误的结果,且每次运行都可能得到不同的结果,因此输出的结果存在不确定性(indeterminate

临界区(critical section):它是访问共享资源的代码片段,一定不能给多线程同时执行。

互斥(mutualexclusion):也就说保证一个线程在临界区执行时,其他线程应该被阻止进入临界区

同步:就是并发进程/线程在一些关键点上可能需要互相等待与互通消息,这种相互制约的等待与互通信息称为进程/线程同步。

注意,同步与互斥是两种不同的概念:

  • 同步就好比:「操作 A 应在操作 B 之前执行」,「操作 C 必须在操作 A 和操作 B 都完成之后才能执行」等;
  • 互斥就好比:「操作 A 和操作 B 不能在同一时刻执行」;

互斥与同步的实现和使用

主要的方法有两种:

  • :加锁、解锁操作;
  • 信号量:P、V 操作;

这两个都可以方便地实现进程/线程互斥,而信号量比锁的功能更强一些,它还可以方便地实现进程/线程同步。

使用加锁操作和解锁操作可以解决并发线程/进程的互斥问题。

任何想进入临界区的线程,必须先执行加锁操作。若加锁操作顺利通过,则线程可进入临界区;在完成对临界资源的访问后再执行解锁操作,以释放该临界资源。

根据锁的实现不同,可以分为。

  • 忙等待锁(自旋锁):获取不到锁时,线程就会一直 while 循环,不做任何事情

    • 单CPU上需要抢占调度,因为在单 CPU 上无法使用,因为一个自旋的线程永远不会放弃 CPU
  • 无等待锁:当没获取到锁的时候,就把当前线程放入到锁的等待队列,然后执行调度程序,把 CPU 让给其他线程执行。

信号量

通常信号量表示资源的数量,对应的变量是一个整型(sem)变量。

另外,还有两个原子操作的系统调用函数来控制信号量的,分别是:

  • P 操作:将 sem1,相减后,如果 sem < 0,则进程/线程进入阻塞等待,否则继续,表明 P 操作可能会阻塞;

    • P 操作是用在进入临界区之前
  • V 操作:将 sem1,相加后,如果 sem >= 0,唤醒一个等待中的进程/线程,表明 V 操作不会阻塞;

    • V 操作是用在离开临界区之后
  • PV 操作的函数是由操作系统管理和实现的,所以操作系统已经使得执行 PV 函数时是具有原子性的。

  • 想要实现互斥要为每类共享资源设置一个信号量 s,其初值为 1

    • 如果互斥信号量为 1,表示没有线程进入临界区;

    • 如果互斥信号量为 0,表示有一个线程进入临界区;

    • 如果互斥信号量为 -1,表示一个线程进入临界区,另一个线程等待进入。

  • 信号量实现事件同步,设置一个信号量,其初值为 0

    • 一个通过P操作之后等待其他的V操作之后执行
    • V操作之后通知P操作执行

生产者 - 消费者问题

生产者 - 消费者问题描述:

  • 生产者在生成数据后,放在一个缓冲区中;
  • 消费者从缓冲区取出数据处理;
  • 任何时刻,只能有一个生产者或消费者可以访问缓冲区;

我们对问题分析可以得出:

  • 任何时刻只能有一个线程操作缓冲区,说明操作缓冲区是临界代码,需要互斥
  • 缓冲区空时,消费者必须等待生产者生成数据;缓冲区满时,生产者必须等待消费者取出数据。说明生产者和消费者需要同步

那么我们需要三个信号量,分别是:

  • 互斥信号量 mutex:用于互斥访问缓冲区,初始化值为 1;
  • 资源信号量 fullBuffers:用于消费者询问缓冲区是否有数据,有数据则读取数据,初始化值为 0(表明缓冲区一开始为空);
  • 资源信号量 emptyBuffers:用于生产者询问缓冲区是否有空位,有空位则生成数据,初始化值为 n(缓冲区大小);

经典同步问题

哲学家就餐问题

哲学家就餐的问题

先来看看哲学家就餐的问题描述:

  • 5 个老大哥哲学家,闲着没事做,围绕着一张圆桌吃面;
  • 巧就巧在,这个桌子只有 5 支叉子,每两个哲学家之间放一支叉子;
  • 哲学家围在一起先思考,思考中途饿了就会想进餐;
  • 奇葩的是,这些哲学家要两支叉子才愿意吃面,也就是需要拿到左右两边的叉子才进餐
  • 吃完后,会把两支叉子放回原处,继续思考

解法一

  • 偶数位置先拿左边再拿右边,奇数位置先拿右边再拿左边,可以两人同时进餐。

解法二

  • 每次只能有一个人能拿叉子,每次只能一个人进餐

解法三

  • 每次每个人只有左右两侧都没有人拿叉子的时候立马拿起两个叉子,可以两人同时进餐

解法四

  • 每个人先拿起右侧叉子再拿起左侧叉子,会出现死锁,满足四个条件

读者 - 写者问题

读写锁问题:通过读写互斥、写写互斥、读读兼容实现

正在精进