windows多线程(九) PV原语分析同步问题

一、PV原语介绍

PV原语通过操作信号量来处理进程间的同步与互斥的问题。其核心就是一段不可分割不可中断的程序。

信号量的概念1965年由著名的荷兰计算机科学家Dijkstra提出,其基本思路是用一种新的变量类型(semaphore)来记录当前可用资源的数量。有两种实现方式:1)semaphore的取值必须大于或等于0。0表示当前已没有空闲资源,而正数表示当前空闲资源的数量;2) semaphore的取值可正可负,负数的绝对值表示正在等待进入临界区的进程个数。

信号量是由操作系统来维护的,用户进程只能通过初始化和两个标准原语(P、V原语)来访问。初始化可指定一个非负整数,即空闲资源总数。

二、PV原语原理

PV操作由P操作原语和V操作原语组成(原语也叫原子操作Atomic Operation,是不可中断的过程),对信号量(注意不要和Windows中的信号量机制相混淆)进行操作,具体定义如下:

PV操作对于每一个进程来说,都只能进行一次,而且必须成对使用。在PV原语执行期间不允许有中断的发生。

P(S):

①将信号量S的值减1,即S=S-1;

②如果S>=0,则该进程继续执行;否则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。


V(S):

①将信号量S的值加1,即S=S+1;

②该进程继续执行;如果该信号的等待队列中有等待进程就唤醒一等待进程。

具体PV原语对信号量的操作可以分为三种情况:

  1. 把信号量视为一个加锁标志位,实现对一个共享变量的互斥访问。实现过程如下:
    P(mutex); // mutex的初始值为1 访问该共享数据;
    V(mutex);
    非临界区

  2. 把信号量视为是某种类型的共享资源的剩余个数,实现对一类共享资源的访问。实现过程如下:
    P(resource); // resource的初始值为该资源的个数N 使用该资源;
    V(resource);
    非临界区

  3. 把信号量作为进程间的同步工具(???)。实现过程如下:
    临界区C1;
    P(S);
    V(S);
    临界区C2;

三、实例分析

(一) 题目

桌上有一空盘,允许存放一只水果。爸爸可向盘中放苹果,也可向盘中放橘子,儿子专等吃盘中的橘子,女儿专等吃盘中的苹果。规定当盘空时一次只能放一只水果供吃者取用,请用P、V原语实现爸爸、儿子、女儿三个并发进程的同步。

(二) 过程分析

  • 先考虑同步情况即所有“等待”情况:
    第一.爸爸要等待盘子为空。
    第二.儿子要等待盘中水果是橘子。
    第三.女儿要等待盘中水果是苹果。

  • 看起来盘子好像是要作互斥处理的,但由于题目中的爸爸、儿子、女儿均只有一个,并且他们访问盘子的条件都不一样,所以他们根本不会同时去访问盘子,因此盘子也就不用作互斥处理了。

  • 先设置三个信号量,信号量Orange表示盘中有橘子,初值为0。信号量Apple表示盘中有苹果,初值为0。信号量EmptyDish表示盘子为空,初值为1。

1.爸爸
P(EmptyDish)
if (rand()%2==0)
{
放橘子
V(Orange)
}
else
{
放苹果
V(Apple)
}

2.儿子
P(Orange)
取橘子
V(EmptyDish)

3.女儿
P(Apple)
取苹果
V(EmptyDish)

(三) 代码实现



#include <iostream>
#include<windows.h>
using namespace std;

DWORD WINAPI  FatherThread(LPVOID);  //爸爸往盘子中放橘子或苹果的线程
DWORD WINAPI  DaughterThread(LPVOID); //女儿取走苹果线程
DWORD WINAPI  SonThread(LPVOID);    //儿子取走橘子线程

HANDLE g_Disk, g_Orange, g_Apple;


int main()
{
	g_Disk = CreateSemaphore(NULL, 1, 1, NULL);   //初始化盘子信号量,设置盘子为空
	g_Orange = CreateSemaphore(NULL, 0, 1, NULL);
	g_Apple = CreateSemaphore(NULL, 0, 1, NULL);

	//启动三个线程
	const int THREAD_NUM = 3;
	HANDLE handle[THREAD_NUM];
	handle[0] = CreateThread(NULL, 0, FatherThread, NULL, 0, NULL);
	handle[1] = CreateThread(NULL, 0, DaughterThread, NULL, 0, NULL);
	handle[2] = CreateThread(NULL, 0, SonThread, NULL, 0, NULL);

	getchar();  //不阻塞输入,用来结束程序
	CloseHandle(g_Disk);
	CloseHandle(g_Apple);
	CloseHandle(g_Orange);
	for (int i = 0; i < THREAD_NUM; i++)
	{
		CloseHandle(handle[i]);
	}
	return 0;
}


DWORD WINAPI  FatherThread(LPVOID)
{
	while (1)
	{
		WaitForSingleObject(g_Disk, INFINITE);
		Sleep(1000);
		if (rand() % 2 == 0)
		{
			cout << "爸爸向盘子中放入了一个橘子!" << endl;
			ReleaseSemaphore(g_Orange, 1, NULL);
		}
		else
		{
			cout << "爸爸向盘子中放入了一个苹果!" << endl;
			ReleaseSemaphore(g_Apple, 1, NULL);
		}
	}


	return 0;
}

DWORD WINAPI  DaughterThread(LPVOID)
{
	while (1)
	{
		WaitForSingleObject(g_Apple, INFINITE);
		Sleep(1000);
		cout << "\t\t\t女儿取走了盘子中的苹果!" << endl;
		ReleaseSemaphore(g_Disk, 1, NULL);
	}

	return 0;
}

DWORD WINAPI  SonThread(LPVOID)
{
	while (1)
	{
		WaitForSingleObject(g_Orange, INFINITE);
		Sleep(1000);
		cout << "\t\t\t儿子取走了盘子中的橘子!" << endl;
		ReleaseSemaphore(g_Disk, 1, NULL);
	}

	return 0;
}


运行结果如下所示:

posted @ 2018-06-03 21:21  ay-a  阅读(1453)  评论(0编辑  收藏  举报