在
OS
中引入进程后,系统中的多道程序能够并发执行,但系统却变得更加复杂,为使进程有序运行,引入了同步机制。在进程之间传送大量数据,也须要利用进程通讯工具。这篇文章总结了进程的几种同步方式和进程之间的通讯方式。数组
为避免竞争条件,操做系统须要利用同步机制在并发执行时,保证对临界区的互斥访问。进程同步的解决方案主要有:信号量和管程。bash
对于同步机制,须要遵循如下四个规则:数据结构
CPU
,如转换到阻塞态;信号量机制(semaphore
)是一种协调共享资源访问的方法。信号量由一个变量 semaphore
和两个原子操做组成,信号量只能经过 P
和 V
操做来完成,并且 P
和 V
操做都是原子操做。并发
将信号量表示以下:工具
typedef struct {
int value;
struct process_control_block *list;
} semaphore;
复制代码
相应的 P(wait)
操做和 V(signal)
操做以下实现:ui
wait(semaphore *S) {
S->value--;
if(S->value < 0) {
block(S->list);
}
}
signal(semaphore *S) {
S->value++;
if(S->value <= 0) {
wakeup(S->list);
}
}
复制代码
信号量可分为两类:互斥信号量,信号量大小为为 0
或 1
,用来实现进程的互斥访问;资源信号量,信号量大小为资源数,用来表示系统资源数目。spa
资源信号量操作系统
表明资源信号量时,S->value
初值表示系统资源的数目,P
操做意味着进程请求一个资源,因而系统中可分配的资源数减一,若是 S->value < 0
,表示该类资源已分配完毕,所以阻塞该进程,并插入信号量链表 S->list
中。小于 0
时,S->value
的绝对值表示该信号量链表中阻塞的进程数。线程
V
操做表示进程释放一个资源,因而系统中可分配的资源数加一,若是增长一后仍然 S->value <= 0
,表示该信号量链表中仍然有阻塞的进程,所以调用 wakeup
,将 S->list
中的第一个进程唤醒。code
互斥信号量
表明互斥信号量时,S->value
初值为 1
,表示只容许一个进程访问该资源。
利用信号量实现两个进程互斥描述以下:
semaphore mutex = 1;
P() {
wait(mutex);
临界区;
signal(mutex);
}
复制代码
当 mutex = 1
时,表示两个进程都没有进入临界区,当 mutex = 0
时,表示一个进程进入临界区运行;当 mutex = -1
时,表示一个进程进入临界区运行,另外一个进程被阻塞在信号量队列中。
管程采用面向对象思想,将表示共享资源的数据结构及相关的操做,包括同步机制,都集中并封装到一块儿。全部进程都只能经过管程间接访问临界资源,而管程只容许一个进程进入并执行操做,从而实现进程互斥。
Monitor monitor_name {
share variable declarations;
condition declarations;
public:
void P1(···) {
···
}
{
initialization code;
}
}
复制代码
管程中设置了多个条件变量,表示多个进程被阻塞或挂起的条件,条件变量的形式为 condition x, y;
,它也是一种抽象数据类型,每一个变量保存了一条链表,记录因该条件而阻塞的进程,与条件变量相关的两个操做:condition.cwait
和 condition.csignal
。
condition.cwait
:正在调用管程的进程因 condition
条件须要被阻塞,则调用 condition.cwait
将本身插入到 condition
的等待队列中,并释放管程。此时其余进程能够使用该管程。condition.csignal
:正在调用管程的进程发现 condition
条件发生变化,则调用 condition.csignal
唤醒一个因 condition
条件而阻塞的进程。若是没有阻塞的进程,则不产生任何结果。生产者-消费者问题描述的是:生产者和消费者两个线程共享一个公共的固定大小的缓冲区,生产者在生成产品后将产品放入缓冲区;而消费者从缓冲区取出产品进行处理。
它须要保证如下三个问题:
利用信号量解决
用信号量解决生产者-消费者问题,使用了三个信号量:
mutex
:用来保证生产者和消费者对缓冲区的互斥访问;full
:记录已填充的缓冲槽数目;empty
:记录空的缓冲槽数目。#define N 10
int in = 0, out = 0;
item buffer[N];
semaphere mutex = 1, full = 0, empty = N;
void producer(void) {
while(TRUE) {
item nextp = produce_item();
wait(empty);
wait(mutex);
buffer[in] = nextp;
in = (in + 1) % N;
signal(mutex);
signal(full);
}
}
void consumer(void) {
while(TRUE) {
wait(full);
wait(mutex);
item nextc = buffer[out];
out = (out + 1) % N;
signal(mutex);
signal(empty);
consume_item(nextc);
}
}
复制代码
须要注意的是进程中的多个 wait
操做顺序不能颠倒,不然可能形成死锁。例如在生产者中,当系统中没有空的缓冲槽时,生产者进程的 wait(mutex)
获取了缓冲区的访问权,但 wait(empty)
会阻塞,这样消费者也没法执行。
利用管程解决
利用管程解决时,须要为它们创建一个管程,其中 count
表示缓冲区中已有的产品数目,条件变量 full
和 empty
有 cwait
和 csignal
两个操做,另外还包括两个过程:
put(x)
:生产者将本身生产的产品放入到缓冲区中,而若是 count >= N
,表示缓冲区已满,生产者须要等待;get(x)
:消费者从缓冲区中取出一个产品,若是 count <= 0
,表示缓冲区为空,消费者应该等待;Monitor producerconsumer {
item buffer[N];
int in, out;
condition full, emtpy;
int count;
public:
void put(item x) {
if(count >= N) {
cwait(full);
}
buffer[in] = x;
in = (in + 1) % N;
count++;
csignal(emtpy);
}
item get() {
if(count <= 0) {
cwait(emtpy);
}
x = buffer[out];
out = (out + 1) % N;
count--;
csignal(full);
}
{ in = 0; out = 0; count = 0; }
}
复制代码
因而生产者和消费者可描述为:
void producer() {
while(TRUE) {
item nextp = produce_item();
producerconsumer.put(nextp);
}
}
void consumer() {
while(TRUE) {
item nextc = producerconsumer.get();
consume_item(nextc);
}
}
复制代码
哲学家就餐问题描述的是:有五个哲学家共用一个圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们交替地进行思考和进餐。哲学家在平时进行思考,在饥饿时试图获取左右两只筷子,拿到两只筷子才能进餐,进餐完后放下筷子继续思考。
为实现筷子的互斥使用,能够用一个信号量表示一只筷子,五个信号量构成信号量数组,也都被初始化为 1
。
semaphore chopstick[5] = {1, 1, 1, 1, 1};
复制代码
第 i
位哲学家的活动可描述为:
void philosopher(int i) {
while(TRUE) {
wait(chopstick[i]);
wait(chopstick[(i + 1) % 5]);
// eat
signal(chopstick[i]);
signal(chopstick[(i + 1) % 5]);
// think
}
}
复制代码
上述解法中,若是五位哲学家同时饥饿而都拿起左边的筷子,再试图去拿右边的筷子时,会出现无限期等待而引发死锁。
读者-写者问题描绘的是:一个文件能够被多个进程共享,容许多个 Reader
进程同时读这个文件,但不容许 Wirter
进程和其余 Reader
进程或 Writer
进程同时访问这个文件。因此读者-写者须要保证一个 Writer
进程必须与其余进程互斥地访问共享对象。
解决这个问题须要设置两个互斥信号量和一个整形变量:
wmutext
:实现 Reader
进程和 Writer
进程在读或写时的互斥;readcount
:正在读的进程数目;rmutext
:实现多个 Reader
进程对 readcount
变量的互斥访问;semaphore rmutex = 1, wmutex = 1;
int readcount = 0;
void Reader() {
while(TRUE) {
wait(rmutex);
if(readcount == 0) {
wait(wmutex);
}
readcount++;
signal(rmutex);
// perform read opertaion
wait(rmutex);
readcount--;
if(readcount == 0) {
signal(wmutex);
}
signal(rmutex);
}
}
void Writer() {
while(TRUE) {
wait(wmutex);
// perform wirte opertaion
signal(wmutex);
}
}
复制代码
只要有一个 Reader
进程在读,便不容许 Writer
进程去写。因此,仅当 readcount = 0
,表示没有 Reader
进程在读时,Reader
进程才须要执行 wait(wmutex)
操做,而 readcount != 0
时,表示有其余 Reader
进程在读,也就确定没有 Writer
在写。同理,仅当 readcount = 0
时,才执行 signal(wmutex)
相似。
进程通讯是指进程之间的信息交换。在进程间要传送大量数据时,应利用高级通讯方法。
在共享内存系统中,多个通讯的进程共享某些数据结构或存储区,进程之间可以经过这些空间进行通讯。
可分为两种类型:
管道(Pipe
)是指用于链接一个读进程和一个写进程以实现进程间通讯的一个共享文件。发送进程以字符形式将数据送入管道,而接收进程则从管道中接收数据。
管道机制提供了三方面的协调能力:
消息传递机制中,进程以格式化的消息为单位,将通讯的数据封装在消息中,并利用操做系统提供的原语,在进程之间进行消息传递,完成进程间数据交换。
按照实现方式,可分为两类: