【操作系统】线程管理

【操作系统】线程管理


线程的管理

为什么需要线程管理

线程管理是对某个程序中全部线程的管理工作,当不同进程对同一资源进行操作时,可能会出现以下问题:

  1. 安全性问题
  2. 活跃性问题
  3. 性能问题

为了使程序可以健壮,稳定,高效的运行,我们需要对线程进行管理。

安全性问题

那么线程的安全性问题是什么?对于程序来讲,在运行时发生错误就是不安全的。所以,对于线程来讲,安全性问题即运行结果的“正确性问题”。

最常见的例子就是:

1
count++;

这个操作其实分为三步:

  1. 首先获取count的值;
  2. 将count的值+1;
  3. 将+1后的结果赋值给count;

当两个线程对同一count变量执行该操作时,由于执行顺序有很多种,所以会产生不同的结果,也就可能会产生错误的结果,这就是线程的安全性问题。

活跃性问题

什么是活跃性问题?当一个线程由于某些原因一直没有被执行,或者所分到的时间片非常少,那么这个线程就存在着活跃性问题。

常见的活跃性问题包括:死锁,饥饿,活锁

死锁

线程A在等待B持有的某种资源(这里是锁),而线程B在等待A持有的某种资源,在没有获得自己想要的资源之前,线程A和线程B都不会主动的释放自己所持有的资源,于是就陷入了“死锁”;

形象一点来讲,就像是幼儿园的两个小朋友吵架了,两个小朋友心理都在想“哼!╭(╯^╰)╮如果你不道歉,我就不道歉”,于是局面就一直僵持着。

还有一种更简单的情况,也是死锁

有A和B两个线程,A和B运行都需要4份资源,总共有6份,此时A和B都获取了3份,此时两者都没有满足运行条件,则产生死锁

形成死锁的条件

  • 互斥条件:一个资源只能由一个进程使用
  • 部分分配条件:一个进程占有一定资源后,期间又在申请其他资源
  • 不可抢占条件:一个资源只能被释放,不能被抢占
  • 循环等待条件:一个进程占有若干资源,同时等待其他资源,多个进程形成循环等待链

利用银行家算法避免死锁

在银行家算法中,资源的分配是批量的,仅仅在申请者获得资源后可以运行,系统才会为其提供资源。

例如上述的例子,A和B两个线程,系统会一次性分配4份资源给A或B,让其执行。

等待图法检测死锁

如上图所示,P1等在P2占有的资源,P2等待P3……,当等待图中形成回路时,代表产生了死锁。

活锁

活锁概念:独木桥两个人互相让路。

在计算机中活锁的情况不太常见。这里就不展开讨论。

饥饿

人的饥饿是因为没有食物吃,而线程的饥饿时因为没有CPU时间片使用,产生这种情况的原因可能是:

  • 线程的优先级太低
  • CPU的调度算法存在问题,或者不适合该程序

性能问题

正如字面意义,如果存在性能问题,说明服务时间过长,相应不灵敏,吞吐量低等。

线程方面出现这类问题的原因通常是“滥用锁”;

引入多线程的目的就是因为程序中的多种任务可以并行执行(参考单核多线程的CPU),但由于频繁加锁,或者锁的细粒度很大,会导致程序很长一部分时间都处于单线程状态,自然会产生性能问题。

如何解决上述问题

有两种经典有效的办法解决上述问题,“正确的”使用PV操作管程

  • 对于安全性问题,产生的原因是不同线程对共享变量的操作时机不同,所以我们只需要保证某一时刻只有一个线程操作共享变量即可,即线程互斥
  • 对于活跃性问题,往往是由于某个线程不能得到他需要的某个资源,所以我们要做好“线程间的同步”工作,即线程同步
  • 对于性能问题,往往是由于“锁的滥用”,所以我们要尽量使用小粒度的锁。

PV操作和管程

PV操作

原语:所谓原语,即不可中断的执行过程

一,信号量

信号量:表示一种资源能否被使用。其数据结构表示如下

1
2
3
4
5
6
semaphore{
//正数时(包括0)表示可使用的资源个数,负数时,表示等待进程的个数
int value;
//进程的阻塞队列
PCB* pointer;
}

注意,信号量只能只能三个操作

  • 初始化
  • 被P操作
  • 被V操作

信号的设置,往往是针对操作的

二,P原语

P代表进程申请使用资源,申请后,资源的信号量value值-1

  • 如果此时value<0,则将此进程插入到阻塞队列中
  • 如果value在-1后≥0,则执行该进程

三,V原语

V代表进程归还(释放)资源,释放后,value值+1

  • 如果+1后,value≤0,则说明原来是小于0的,说明阻塞队列中有等待资源的进程,+1代表释放一个资源,所以要执行一个阻塞队列中的进程
  • 如果+1后,value>0,则说明没有被阻塞的进程,只是归还一个待使用的资源而已

管程

管程可以看做是一个盒子,他把共享变量和对共享变量的操作封装到一起,并且盒子(管程)中只有一个位置,即同一时刻只允许一个进程进入管程。

这里可能看起来很抽象,具体的使用方法请参考下文。

进程互斥

什么是进程互斥?进程互斥就是不允许两个进程同时进入临界区(共享内存),去操作临界资源(共享资源)。

使用PV操作解决互斥问题

  1. 定义信号量mutex,由于是实现互斥,所以初始值为1(表示只有一个资源可以被使用)
  2. 使用P操作检测mutex
  3. 使用V操作检测mutex

程序的实现大致如下:

1
2
3
4
5
6
7
8
9
10
semaphore mutex;
mutex.value=1;

process pi{
......
P(mutex);
临界区代码......
V(mutex);
.......
}

使用管程解决互斥问题

image-20191122140441679

如上图所示,由于管程中的预留位置为1,所以同一时刻只能有一个线程进入管程。那么管程是怎么保证只有一个的呢? 因为管程使用了 入口等待队列 。要想进入管程就要先进入队列排队。

这样就保证了同一时刻只有一个线程操作共享变量,解决了线程互斥的问题。

进程同步

什么是进程的同步?同步表示不同进程种相互制约的执行关系。即要想执行某项任务,必须满足某个前提条件。

使用PV操作解决同步问题

同步的信号量往往是针对操作本身的(也就是说该操作是否可以被执行)

而互斥的信号量往往代表一种资源是否可以被使用

最常见的同步例子:生产者-消费者,缓冲区大小为n,消费者如果想要进行消费,那么商品队列中一定要有商品,具体实现如下:

使用管程解决同步问题

  1. 首先假设消费者线程先进入管程中,要进行消费执行decrease操作的条件变量是full=true,但是当前条件不满足,于是此线程被加入到对应条件变量的等待队列中
  2. 此时生产者线程进入,发现empty=true,于是执行increase操作
  3. 在执行increase后,条件变量full=true,于是释放相应队列中的线程

同步和互斥结合

生产者-消费者(这里的缓冲区大小要求为K[k大于1])

  • 这里的同步指的是:
    • 消费者在消费前,队列中要有产品;
    • 生产者在生产前,产品队列不满;
  • 互斥指的是对临界资源的互斥操作,既同一时刻只能有一个线程对产品队列进行操作

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×