进程同步、异步

异步性:进程的异步性:各并发执行的进程以各自独立的、不可预知的速度向前推进。
同步性:直接制约关系,为了完成某种任务而建立的两个或多个进程,这些进程在某些位置上协调工作次序而产生制约关系。

进程通信方式:①共享存储 ②管道通信 ③消息传递

对临界资源进行访问的那段代码称为临界区
为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。

// entry section(上锁)
// critical section;
// exit section(解锁)

image

进程互斥四个原则:

image
进程互斥:进程之间无制约关系,他们只是想要互斥的访问某临界资源。
进程同步:进程之间有一个制约关系

进程互斥的软件实现方法:

1、单标志位法

image
缺点:0-1-0-1……如果其中一个进程卡住了,另一个就会发生饥饿,违背”空闲让进“原则。

2、双标志先检查法:

image
如果两个进程并发执行,就会发生

3、双标志后检查法:

image

4、Peterson算法

image
缺点:让权等待

硬件:中断屏蔽、TestAndSet指令、swap指令

信号量机制--信号量两种类型

信号量是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。
down: 如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0,进程睡眠,等待信号量大于 0;
up:对信号量执行 +1 操作,唤醒睡眠的进程让其完成 down 操作。

整形信号量:(不满足 让权等待)

image

记录型信号量:

image
wakeup():唤醒队列
bloak():运行态->阻塞态

当S.value<0时,表示该资源已分配完毕,调用block原语自我阻塞,主动放弃cpu。运行态->阻塞态
当S.value<=0时wakeup(),表示依然有进程等待该队列,因此调用wakeup原语唤醒等待队列中的第一个进程。阻塞态--就绪态

<

信号量机制实现互斥、同步

信号量机制实现互斥:

临界区理解为特殊的资源,只有一个,mutex = 1
对不同的临界资源需要设置不同的互斥信号量:mutex1、mutex2……
P、V 必须成对出现

信号量机制实现同步(同步信号量初始为0)

image

前V后P
image

image

生产者消费者问题

使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。

互斥:设置初值= 资源数量 的互斥信号量(P、V成对);(打印机)(临界区数量,一般设置为1)
同步:设置初值0同步信号量(前V后P)(前驱关系),要看对相应资源初始值n

image
image
image

image
实现互斥的操作在实现同步操作之后。

#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;	// 空闲缓冲区数量,刚开始时N
semaphore full = 0;	// 非空缓冲区数量,刚开始时空的

void producer()
{
    while(TRUE)
	{
        int item = produce_item();	// 生产产品
        P(&empty);
        P(&mutex);
        insert_item(item);	// 放入缓冲区
        V(&mutex);
        V(&full);
    }
}

void consumer()
{
    while(TRUE)
	{
        P(&full);
        P(&mutex);
        int item = remove_item();
        consume_item(item);
        V(&mutex);
        V(&empty);
    }
}

使用:P 提供:V

#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore NowResources = N;	// 当前剩余空闲缓冲区数量,刚开始时N
semaphore ProductCount = 0;	// 当前产品数量,刚开始时空的

void producer()
{
    while(1)
	{
        int item = produce_item();	// 生产产品
        P(&NowResources);
        P(&mutex);
        insert_item(item);	// 放入缓冲区
        V(&mutex);
        V(&ProductCount);	// 既然走到这,缓冲区一定有产品
    }
}

void consumer()
{
    while(1)
	{
        P(&ProductCount);
        P(&mutex);
        int item = remove_item();
        consume_item(item);
        V(&mutex);
        V(&NowResources);	// 使用产品,缓冲区增加1
    }
}

代码不能放进临界区,会造成临界区代码变大,会降低进程之间的并发度。

多生产者-多消费者

不同类彼得生产者,不同类别的消费者

#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore Resources = N;	// 当前剩余空闲缓冲区数量,开始为N
semaphore ProductCount1 = 0;	// 当前产品数量,开始为空
semaphore ProductCount2 = 0;	// 当前产品数量,

void producer1()
{
    while(1)
	{
        int item = produce_item();	// 生产产品
        P(&Resources);
        P(&mutex);
        insert_item(item);	// 放入缓冲区
        V(&mutex);
        V(&ProductCount1);	// 既然走到这,缓冲区一定有产品
    }
}
void producer2()
{
    while(1)
	{
        int item = produce_item();	// 生产产品
        P(&Resources);
        P(&mutex);
        insert_item(item);	// 放入缓冲区
        V(&mutex);
        V(&ProductCount2);	// 既然走到这,缓冲区一定有产品
    }
}

void consumer1()
{
    while(1)
	{
        P(&Product1);
        P(&mutex);
        int item = remove_item();
        consume_item(item);
        V(&mutex);
        V(&Resources);	// 使用产品,缓冲区增加1
    }
}
void consumer2()
{
    while(1)
	{
        P(&Product2);
        P(&mutex);
        int item = remove_item();
        consume_item(item);
        V(&mutex);
        V(&Resources);	// 使用产品,缓冲区增加1
    }
}

当资源 > 1 时,要设置互斥信号量

吸烟者模式(单生产者--多消费者)

image

读者--写者

允许多个进程同时对数据进行读操作,但不允许读写 以及 写写操作同时发生。

一个整型变量 count 记录在对数据进行读操作的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁。
image

image
可能无法实现并发读取数据:原因在于对count变量得检查和复制无法一气呵成,因此可以通过设置另一个互斥信号量来保证各读进程对count得访问是互斥的。
image

为了改变写者饥饿;一旦写着想写,那么就不能有新得读者读文件了。(先来先服务

image

读者写者问题核心:设计一个计数器count记录当前正在访问共享文件得都进程数。通过count得值来判断当前进入得进程是否是第一个/最后一个都进程,从而做出不同的处理。
一气呵成操作:互斥信号量

哲学家问题(解决死锁)

该问题只有互斥问题。每一个进程需要同时持有两个临界资源才可以执行任务。避免临界资源分配不当造成死锁现象。
信号量设置。定义互斥信号量数组 chopstick[5] = {1,1,1,1,1} 用于实现五支筷子得互斥访问。并对哲学家编号:0~4,哲学家i左边的筷子编号为i,右边为(i+1)%5

semaphore chopstick[5] = {1,1,1,1,1};
Pi()
{
	while(1)
	{
		P(chopstick[i]);
		P(chopstick[i+1]%5);
		eat();
		V(chopstick[i]);
		V(chopstick[i+1]%5);
		think();
	}
}

问题:当五个科学家同时吃饭,分别拿起做百年的筷子,思索。
解决办法:①最多允许四个哲学家同时进餐,保证至少有一个哲学家可以拿到两只筷子。②奇数号先拿左边筷子,再拿右边筷子;偶数号先拿右边的筷子,再拿左边筷子。这样就能保证,未拿到筷子得进程阻塞,而不会出现拿到筷子阻塞。③仅当一个哲学家左右筷子都可以使用,才允许他使用筷子。
第三种解决办法:
image

管程

为什么引入管程:信号量机制存在问题:编写程序难、易出错。设计一种机制,让程序员不用关注复杂得PV操作。
管程是一种高级的同步机制。
image

基本概念
在信号量机制中,每个要访问临界资源的进程都必须自备同步的PV操作,大量分散的同步操作会给系统管理带来麻烦,且容易因为同步操作不当而导致系统死锁。

管程组成:是一个临界资源管理模块,其中包含了共享资源的数据结构,以及由对该共享数据结构实施操作的一组方法所组成的资源管理程序。还有对数据结构进行初始化的语句。
管程基本特征:①各个进程只能通过管程提供的特定入口才能访问共性数据;
②每次只允许一个进程在管城内执行某个过程。

管程中包含条件变量,用于管理进程的阻塞和唤醒。其形式为 condition x;对它的操作仅有wait和signal。

x.wait:正在调用管程的进程因 x 条件需要被阻塞或挂起,则调用 x.wait 将自己插入到 x 条件的等待队列上,并释放管程,直到 x 条件变化。此时其它进程可以使用该管程。

x.signal:正在调用管程的进程发现 x 条件发生了变化,则调用 x.signal,重新启动一个因 x 条件而阻塞或挂起的进程。(与信号量的signal不同,没有s:=s+1的操作)

个人理解:
管程相当于把对临界资源的操作封装了起来,当进程要对资源进行操作时,只要调用管程中的方法就可以了,而不用进程自己担心同步和互斥的问题,管程的内部有自己的一套机制进行同步与互斥。
管程中每次只允许一个进程进入管程。

实际应用:
生产者-消费者问题
producer-consumer管程的描述:

// 管程
monitor ProducerConsumer
    condition full, empty;
    integer count := 0;
    condition c;

    procedure insert(item: integer);
    begin
        if count = N then wait(full);
        insert_item(item);
        count := count + 1;
        if count = 1 then signal(empty);
    end;

    function remove: integer;
    begin
        if count = 0 then wait(empty);
        remove = remove_item;
        count := count - 1;
        if count = N -1 then signal(full);
    end;
end monitor;

// 生产者客户端
procedure producer
begin
    while true do
    begin
        item = produce_item;
        ProducerConsumer.insert(item);
    end
end;

// 消费者客户端
procedure consumer
begin
    while true do
    begin
        item = ProducerConsumer.remove;
        consume_item(item);
    end
end;
posted @ 2021-05-14 11:42  点|滴  阅读(40)  评论(0)    收藏  举报