线程之间通信的两个基本问题是互斥和同步。
线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的操作系统资源(指的是广义的"资源",而不是Windows的.res文件,譬如全局变量就是一种共享资源),在各线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥是一种特殊的线程同步。实际上,互斥和同步对应着线程间通信发生的两种情况:
(1)当有多个线程访问共享资源而不使资源被破坏时;
(2)当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。在WIN32中,同步机制主要有以下几种:
(1)事件(Event);
(2)信号量(semaphore);
(3)互斥量(mutex);
(4)临界区(Critical section)。
全局变量 因为进程中的所有线程均可以访问所有的全局变量,因而全局变量成为Win32多线程通信的最简单方式。例如:
int var; //全局变量
UINT ThreadFunction(LPVOIDpParam)
{
var = 0;
while (var < MaxValue)
{
//线程处理
::InterlockedIncrement(long*) &var);
}
return 0;
}
请看下列程序:
int globalFlag = false;
DWORD WINAPI ThreadFunc(LPVOID n)
{
Sleep(2000);
globalFlag = true;
return 0;
}
int main()
{
HANDLE hThrd;
DWORD threadId;
hThrd = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &threadId);
if (hThrd)
{
printf("Thread launched/n");
CloseHandle(hThrd);
}
while (!globalFlag) ;
printf("exit/n");
}
上述程序中使用全局变量和while循环查询进行线程间同步,实际上,这是一种应该避免的方法,因为:
(1)当主线程必须使自己与ThreadFunc函数的完成运行实现同步时,它并没有使自己进入睡眠状态。由于主线程没有进入睡眠状态,因此操作系统继续为它调度C P U时间,这就要占用其他线程的宝贵时间周期;
(2)当主线程的优先级高于执行ThreadFunc函数的线程时,就会发生globalFlag永远不能被赋值为true的情况。因为在这种情况下,系统决不会将任何时间片分配给ThreadFunc线程。
事件 事件(Event)是WIN32提供的最灵活的线程间同步方式,事件可以处于激发状态(signaled or true)或未激发状态(unsignal or false)。根据状态变迁方式的不同,事件可分为两类:
(1)手动设置:这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。 (2)自动恢复:一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。
创建事件的函数原型为:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // SECURITY_ATTRIBUTES结构指针,可为NULL
BOOL bManualReset, // 手动/自动 // TRUE:在WaitForSingleObject后必须手动调用ResetEvent清除信号 // FALSE:在WaitForSingleObject后,系统自动清除事件信号
BOOL bInitialState, //初始状态 LPCTSTR lpName //事件的名称 );
使用"事件"机制应注意以下事项:
(1)如果跨进程访问事件,必须对事件命名,在对事件命名的时候,要注意不要与系统命名空间中的其它全局命名对象冲突;
(2)事件是否要自动恢复;
(3)事件的初始状态设置。
由于event对象属于内核对象,故进程B可以调用OpenEvent函数通过对象的名字获得进程A中event对象的句柄,然后将这个句柄用于ResetEvent、SetEvent和WaitForMultipleObjects等函数中。此法可以实现一个进程的线程控制另一进程中线程的运行,例如:
HANDLE hEvent=OpenEvent(EVENT_ALL_ACCESS,true,"MyEvent");
ResetEvent(hEvent);