http://www.vchome.net/tech/multithread/thread23.htm
1.
互斥体对象、信号对象和事件对象也可以用于进程间的线程同步操作。在用Win32函数创建了对象时,我们可以指定对象的名字,还可以设置同步对象在子进程的继承性。创建返回的是HANDLE句柄,我们可以用函数DuplicateHandle来复制对象句柄,这样每个进程都可以拥有同一对象的句柄,实现进程之间的线程同步操作。另外,在同一进程内,我们可以用OpenMutex、OpenSemaphore和OpenEvent来获得指定名字的同步对象的句柄。
排斥区异步执行的线程同步方法只能用于同一进程的线程之间共享资源处理,但是这种方法的使用效率较高,而且编程也相对简单一些。
2.
(1)如果某个线程必须等待某些事件发生后才能存取相应资源,则用CEvent;
(2)如果一个应用同时可以有多个线程存取相应资源,则用CSemaphore;
(3)如果有多个应用(多个进程)同时存取相应资源,则用CMutex,否则用CCriticalSection。
3.
主线程和辅助线程间的通信方式有很多种,最简单的就是利用全局变量。 这里利用消息通信是行不通的,因为辅助线程没有消息循环,不能够利用Windows消息。
4.
Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。
1. 临界区
临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:
#include "afxmt.h"
int array[10],destarray[10];
CCriticalSection Section;
UINT WriteThread(LPVOID param)
{Section.Lock();
for(int x=0;x<10;x++)
array[x]=x;
Section.Unlock();
}
UINT ReadThread(LPVOID param)
{
Section.Lock();
For(int x=0;x<10;x++)
Destarray[x]=array[x];
Section.Unlock();
}
上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。
2. 互斥
互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:
#include "afxmt.h"
int array[10],destarray[10];
CMutex Section;
/
UINT WriteThread(LPVOID param)
{ CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();
for(int x=0;x<10;x++)
array[x]=x;
singlelock.Unlock();
}
UINT ReadThread(LPVOID param)
{ CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();
For(int x=0;x<10;x++)
Destarray[x]=array[x];
singlelock.Unlock();
}
3. 信号量
信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。
/
Csemaphore *semaphore;
Semaphore=new Csemaphore(2,2);
HWND hWnd=GetSafeHwnd();
AfxBeginThread(threadProc1,hWnd);
AfxBeginThread(threadProc2,hWnd);
AfxBeginThread(threadProc3,hWnd);
//
UINT ThreadProc1(LPVOID param)
{CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);
return 0;
}
UINT ThreadProc2(LPVOID param)
{CSingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);
return 0;
}
UINT ThreadProc3(LPVOID param)
{CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);
return 0;
}
5.
critical section(临界区)用来实现“排他性占有”。适合范围是单一进程的各线程之间。它是:
1,一个局部性对象,不是一个核心对象。
2,快速而有效率。
3,不能够同时有一个以上的critical seciton被等待。
4,无法侦测是否被某个线程放弃。
mutex是一个核心对象,可以再不同的线程之间实现“排他性占有”,甚至记是那些线程分属不同的进程。它是:
1,一个核心对象。
2,如果拥有mutex的那个线程结束,则会产生一个"abandoned"错误信息。
3,可以使用wait...()等待一个mutex.
4,可以具名,因此可以被其他进程开启。
5,只能被拥有它的那个线程释放。
semaphore被用来追踪有限的资源,它是:
1,一个核心对象。
2,没有拥有者。
3,可以具名,因此可以被其他进程开启。
4,可以被任何一个线程释放。
event object通常是用于overlapped i/o,或用来设计某些自定义的同步对象。它是:
1,一个核心对象。
2,完全在程序的掌控之下。
3,适用于设计新的同步对象。
4,"要求苏醒"的请求并不会被存贮起来,可能会遗失掉。
5,可以具名,因此可以被其他进程开启。
ccriticalsection 临界区,效率高,不宜长期占用,一次一个占用
cevent 内护事件,方便的控制,用于控制处理顺序,可以多个等待,一次一个占用
cmutex 互斥量,内核对象,一次一个占用
csemaphore 信号量,可以多个同时使用