哲学家就餐问题的定义,选自Andrew S. Tanenbaum所著的Mordern Operating Systems(《现代操作系统》)第三版。作者在书中提供了解决方案。
哲学家就餐问题的定义,选自Andrew S. Tanenbaum所著的Mordern Operating Systems(《现代操作系统》)第三版。作者在书中提供了解决方案。
1965年,Dijkstra提出并解决了一个同步问题,他称之为哲学家就餐问题。这个问题简单地描述如下:5位哲学家围坐在一张圆桌边。每位哲学家前面都放着一盘意大利面条。面条很滑,要用两个餐叉才吃得到。相邻两个盘子之间有一个餐叉。桌上的布局如下图所示。
哲学家就餐问题
假设哲学家的状态是吃面条和思考交替进行。如果哲学家饿了,就会尝试拿起他左边和右边的餐叉,一次只能拿一把。如果成功获得两把餐叉,他就吃一会意大利面,然后放下餐叉,继续思考。关键问题是:能否写一个程序,描述每位哲学家应该怎么做才一定不会卡壳?
我们可以等指定的餐叉可用时才去拿。不过,这样想显然是错误的。如果5位哲学家都同时拿起左边的餐叉,就没人能拿到右边的餐叉,这就出现了死锁。
我们可以修改一下程序,在拿起左边餐叉后,程序检查右边的餐叉是否可用。如果不可用,该哲学家就放下已拿起的左边餐叉,等待一段时间,再重复这一过程。虽然这个解法和上一个解法不同,但是好不到哪里去,也是错误的。如果很不巧,所有的哲学家都同时以该算法开始,拿起他们左边的餐叉,发现右边餐叉不可用,然后放下左边餐叉,等待一会,又同时拿起左边的餐叉……这样永无止尽。这种所有程序无限期不停运行却没有任何进展的情况,叫做饥饿(starvation)。
要实现既不会发生死锁也不会发生饥饿,就要保护“思考”(通过互斥量调用)后面的5个语句。哲学家在开始拿起餐叉之前,要先询问互斥量。互斥量代表相互排斥或者能给对象提供独占访问。在放下餐叉后,哲学家要释放互斥量。理论上,这种解决方案可行。但这实际上有一个性能问题:在任意时刻,只有一个哲学家进餐。桌上有5个餐叉可用,应该能让两个哲学家同时进餐。
下面给出了完整的解决方案。
确定安装并运行了Visual Studio。
1. 创建一个新的默认Win32项目,命名为PhilosophersDinner
。
2. 打开stdafx.h
,并输入下面的代码:
#pragma once
#include "targetver.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <stdio.h>
#pragma comment ( lib, "comctl32.lib" )
#pragma comment ( linker, "\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' \
version='6.0.0.0' processorArchitecture='*' \
publicKeyToken='6595b64144ccf1df' language='*'\"" )
3. 打开PhilosophersDinner.cpp
,并输入下面的代码:
#include "stdafx.h"
#define BUTTON_CLOSE 100
#define PHILOSOPHER_COUNT 5
#define WM_INVALIDATE WM_USER + 1
typedef struct _tagCOMMUNICATIONOBJECT
{
HWND hWnd;
bool bExitApplication;
int iPhilosopherArray[PHILOSOPHER_COUNT];
int PhilosopherCount;
} COMMUNICATIONOBJECT, *PCOMMUNICATIONOBJECT;
HWND InitInstance(HINSTANCE hInstance, int nCmdShow);
ATOM MyRegisterClass(HINSTANCE hInstance);
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam);
int PhilosopherPass(int iPhilosopher);
void FillEllipse(HWND hWnd, HDC hDC, int iLeft, int iTop, int
iRight, int iBottom, int iPass);
TCHAR* szTitle = TEXT("Philosophers Dinner Demo");
TCHAR* szWindowClass = TEXT("__PD_WND_CLASS__");
TCHAR* szSemaphoreName = TEXT("__PD_SEMAPHORE__");
TCHAR* szMappingName = TEXT("__SHARED_FILE_MAPPING__");
PCOMMUNICATIONOBJECT pCommObject = NULL;
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE
hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
HANDLE hMapping = CreateFileMapping((HANDLE)-1, NULL,
PAGE_READWRITE, 0, sizeof(COMMUNICATIONOBJECT), szMappingName);
if (!hMapping)
{
MessageBox(NULL, TEXT("Cannot open file mapping"),
TEXT("Error!"), MB_OK);
return 1;
}
pCommObject = (PCOMMUNICATIONOBJECT)MapViewOfFile(hMapping,
FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (!pCommObject)
{
MessageBox(NULL, TEXT("Cannot get access to file mapping! "),
TEXT("Error!"), MB_OK);
CloseHandle(hMapping);
return 1;
}
InitCommonControls();
MyRegisterClass(hInstance);
HWND hWnd = NULL;
if (!(hWnd = InitInstance(hInstance, nCmdShow)))
{
return FALSE;
}
pCommObject->bExitApplication = false;
pCommObject->hWnd = hWnd;
memset(pCommObject->iPhilosopherArray, 0,
sizeof(*pCommObject->iPhilosopherArray));
pCommObject->PhilosopherCount = PHILOSOPHER_COUNT;
HANDLE hSemaphore = CreateSemaphore(NULL,
int(PHILOSOPHER_COUNT / 2), int(PHILOSOPHER_COUNT / 2),
szSemaphoreName);
STARTUPINFO startupInfo[PHILOSOPHER_COUNT] =
{ { 0 }, { 0 }, { 0 }, { 0 }, { 0 } };
PROCESS_INFORMATION processInformation[PHILOSOPHER_COUNT] =
{ { 0 }, { 0 }, { 0 }, { 0 }, { 0 } };
HANDLE hProcesses[PHILOSOPHER_COUNT];
TCHAR szBuffer[8];
for (int iIndex = 0; iIndex < PHILOSOPHER_COUNT; iIndex++)
{
#ifdef UNICODE
wsprintf(szBuffer, L"%d", iIndex);
#else
sprintf(szBuffer, "%d", iIndex);
#endif
if (CreateProcess(TEXT("..\\Debug\\Philosopher.exe"),
szBuffer, NULL, NULL,
FALSE, 0, NULL, NULL, &startupInfo[iIndex],
&processInformation[iIndex]))
{
hProcesses[iIndex] = processInformation[iIndex].hProcess;
}
}
MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
pCommObject->bExitApplication = true;
UnmapViewOfFile(pCommObject);
WaitForMultipleObjects(PHILOSOPHER_COUNT, hProcesses, TRUE, INFINITE);
for (int iIndex = 0; iIndex < PHILOSOPHER_COUNT; iIndex++)
{
CloseHandle(hProcesses[iIndex]);
}
CloseHandle(hSemaphore);
CloseHandle(hMapping);
return (int)msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wndEx;
wndEx.cbSize = sizeof(WNDCLASSEX);
wndEx.style = CS_HREDRAW | CS_VREDRAW;
wndEx.lpfnWndProc = WndProc;
wndEx.cbClsExtra = 0;
wndEx.cbWndExtra = 0;
wndEx.hInstance = hInstance;
wndEx.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
wndEx.hCursor = LoadCursor(NULL, IDC_ARROW);
wndEx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndEx.lpszMenuName = NULL;
wndEx.lpszClassName = szWindowClass;
wndEx.hIconSm = LoadIcon(wndEx.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
return RegisterClassEx(&wndEx);
}
HWND InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPED
| WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 200, 200, 540, 590,
NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return NULL;
}
HFONT hFont = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE,
FALSE, BALTIC_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH |
FF_MODERN, TEXT("Microsoft Sans Serif"));
HWND hButton = CreateWindow(TEXT("BUTTON"), TEXT("Close"),
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, 410, 520, 100,
25, hWnd, (HMENU)BUTTON_CLOSE, hInstance, NULL);
SendMessage(hButton, WM_SETFONT, (WPARAM)hFont, TRUE);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return hWnd;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case BUTTON_CLOSE:
{
DestroyWindow(hWnd);
break;
}
}
break;
}
case WM_INVALIDATE:
{
InvalidateRect(hWnd, NULL, TRUE);
break;
}
case WM_PAINT:
{
PAINTSTRUCT paintStruct;
HDC hDC = BeginPaint(hWnd, &paintStruct);
FillEllipse(hWnd, hDC, 210, 10, 310, 110,
PhilosopherPass(1));
FillEllipse(hWnd, hDC, 410, 170, 510, 270,
PhilosopherPass(2));
FillEllipse(hWnd, hDC, 335, 400, 435, 500,
PhilosopherPass(3));
FillEllipse(hWnd, hDC, 80, 400, 180, 500,
PhilosopherPass(4));
FillEllipse(hWnd, hDC, 10, 170, 110, 270,
PhilosopherPass(5));
EndPaint(hWnd, &paintStruct);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
return 0;
}
int PhilosopherPass(int iPhilosopher)
{
return pCommObject->iPhilosopherArray[iPhilosopher - 1];
}
void FillEllipse(HWND hWnd, HDC hDC, int iLeft, int iTop, int
iRight, int iBottom, int iPass)
{
HBRUSH hBrush = NULL;
if (iPass)
{
hBrush = CreateSolidBrush(RGB(255, 0, 0));
}
else
{
hBrush = CreateSolidBrush(RGB(255, 255, 255));
}
HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
Ellipse(hDC, iLeft, iTop, iRight, iBottom);
SelectObject(hDC, hOldBrush);
DeleteObject(hBrush);
}
4.右键单击【解决方案资源管理器】,并添加一个新的默认Win32控制台应用程序,命名为Philosopher
。
5. 打开stdafx.h
,并输入下面的代码:
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
6. 打开Philosopher.cpp,并输入下面的代码:
#include "stdafx.h"
#include <Windows.h>
#define EATING_TIME 1000
#define PHILOSOPHER_COUNT 5
#define WM_INVALIDATE WM_USER + 1
typedef struct _tagCOMMUNICATIONOBJECT
{
HWND hWnd;
bool bExitApplication;
int iPhilosopherArray[PHILOSOPHER_COUNT];
int PhilosopherCount;
} COMMUNICATIONOBJECT, *PCOMMUNICATIONOBJECT;
void Eat();
TCHAR* szSemaphoreName = TEXT("__PD_SEMAPHORE__");
TCHAR* szMappingName = TEXT("__SHARED_FILE_MAPPING__");
bool bExitApplication = false;
int _tmain(int argc, _TCHAR* argv[])
{
HWND hConsole = GetConsoleWindow();
ShowWindow(hConsole, SW_HIDE);
int iIndex = (int)_tcstol(argv[0], NULL, 10);
HANDLE hMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE,
szMappingName);
while (!bExitApplication)
{
HANDLE hSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE,
szSemaphoreName);
WaitForSingleObject(hSemaphore, INFINITE);
PCOMMUNICATIONOBJECT pCommObject = (PCOMMUNICATIONOBJECT)
MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0,
sizeof(COMMUNICATIONOBJECT));
bExitApplication = pCommObject->bExitApplication;
if (!pCommObject->iPhilosopherArray[
(iIndex + pCommObject->PhilosopherCount - 1)
% pCommObject->PhilosopherCount]
&& !pCommObject->iPhilosopherArray[
(iIndex + 1) % pCommObject->PhilosopherCount])
{
pCommObject->iPhilosopherArray[iIndex] = 1;
Eat();
}
SendMessage(pCommObject->hWnd, WM_INVALIDATE, 0, 0);
pCommObject->iPhilosopherArray[iIndex] = 0;
UnmapViewOfFile(pCommObject);
ReleaseSemaphore(hSemaphore, 1, NULL);
CloseHandle(hSemaphore);
}
CloseHandle(hMapping);
return 0;
}
void Eat()
{
Sleep(EATING_TIME);
}
我们要创建5个进程来模仿5位哲学家的行为。每位哲学家(即,每个进程)都必须思考和进餐。哲学家需要两把餐叉才能进餐,在餐叉可用的前提下,他必须先拿起左边的餐叉,再拿起右边的餐叉。如果两把餐叉都可用,他就能顺利进餐;如果另一把餐叉不可用,他就放下已拿起的左边餐叉,并等待下一次进餐。我们在程序中把进餐时间设置为1秒。
PhilosophersDinner
是该主应用程序。我们创建了文件映射,可以与其他进程通信。创建Semaphore
对象同步进程也很重要。根据前面的分析,使用互斥量能确保同一时刻只有一位哲学家进餐。这种虽然方法可行,但是优化得不够。如果每位哲学家需要两把餐叉才能进餐,可以用FLOOR( NUMBER_OF_PHILOSOPHERS / 2 )
实现两位哲学家同时进餐。这就是我们设置同一时刻最多有两个对象可以传递信号量的原因,如下代码所示:
HANDLE hSemaphore = CreateSemaphore( NULL,
int( PHILOSOPHER_COUNT / 2 ),
int( PHILOSOPHER_COUNT / 2 ), szSemaphoreName );
这里注意,信号量最初可以允许一定数量的对象通过,但是通过CreateSemaphoreAPI
的第3个参数可以递增这个数量。不过在我们的示例中,用不到这个特性。
初始化信号量对象后,就创建了进程,应用程序可以进入消息循环了。分析完PhilosophersDinner
,我们来看Philosopher
应用程序。这是一个控制台应用程序,因为我们不需要接口,所以将隐藏它的主窗口(本例中是控制台)。如下代码所示:
HWND hConsole = GetConsoleWindow( );
ShowWindow( hConsole, SW_HIDE );
接下来,该应用程序要获得它的索引(哲学家的姓名):
int iIndex = ( int ) _tcstol( argv[ 0 ], NULL, 10 );
然后,哲学家必须获得文件映射对象的句柄,并进入消息循环。在消息循环中,哲学家询问传递过来的信号量对象,等待轮到他们进餐。当哲学家获得一个传入的信号量时,就可以获得两把餐叉。然后,通过下面的SendMessage
API,发送一条消息,更新主应用程序的用户接口:
SendMessage( pCommObject->hWnd, WM_INVALIDATE, 0, 0 );
所有的工作完成后,哲学家会释放信号量对象并继续执行。
本文节选自《C++多线程编程实战》
内容简介
本书是一本实践为主、通俗易懂的Windows多线程编程指导。本书使用C++本地调用,让读者能快速高效地进行并发编程。全书共8章。第1章介绍了C++编程语言的概念和特性。
第2~5章介绍了进程、线程、同步、并发的相关知识。其中,第2章介绍进程和线程的基本概念,详细介绍了进程和线程对象。第3章讲解线程管理方面的知识,以及进程和线程背后的逻辑,简要介绍了线程同步、同步对象和同步技术。第4章重点介绍了消息传递技术、窗口处理器、消息队列和管道通信。第5章介绍了线程同步和并发操作,讲解了并行、优先级、分发器对象和调度技术,解释了同步对象(如互斥量、信号量、事件和临界区)。
第6章介绍.NET框架中的线程,概述了C++/CLI .NET线程对象。简要介绍了托管方法、.NET同步要素、.NET线程安全、基于事件的异步模式和BackgroundWorker对象,以及其他主题。
第7~8章为水平较高的读者准备了一些高级知识,概述了并发设计和高级线程管理。其中,第7章讲解理解并发代码设计,涵盖了诸如性能因素、正确性问题、活跃性问题的特性。第8章讲解高级线程管理,重点介绍更高级的线程管理知识。详细介绍了线程池的抽象、定制分发对象,以及死锁的解决方案。
附录涵盖了MySQL Connector C和WinDDK的具体安装步骤,介绍了如何为驱动程序编译和OpenMP编译设置Visual Studio。另外,还介绍了DebugView应用程序的安装步骤,并演示了它的使用步骤。
本书主要面向中高级读者,可作为用C++进行Windows多线程编程的参考读物。本书介绍的同步概念非常基础,因此也可作为对这方面技术感兴趣的读者和开发人员的参考书籍。