WIN32多线程程序设计学习笔记(第四章 上)
到目前为止,我已经知道如何创建线程,销毁线程及如何判断一个线程是否结束了;但运用线程,管理线程,如何使线程按自己意愿正确运行才是多线程程序设计的重点及难点。
大家知道,线程具有并发性,在同一时间段会存在多个线程,当这些线程同时存取同一数据时,就会有问题。就像在超市储物品一样,来的时候物品箱是空,转身拿物品准备储的时候,发现物品箱已被占用了。这时,物品箱就是我所说的同一数据,人指的就是线程了。
那么,如何让线程彼此互相合作,互不干扰呢?------协调线程的工作是由同步机制来完成的。正如书上所说:“有放多种同步机制可以运用。使用哪一种则完全视欲解决的问题而定。这些同步机制还能用各种方式组合在一起运用,以产生更精密的机制”。
首先,让我们看看最容易使用的一个同步机制;它包含同步机制的设计思想,拿握了它也就可以不费什么劲拿握其它的同步机制了。
1 :Critical Sections(关键区域、临界区域)
程序片段:
CRITICAL_SECTION gBoxKey ;
DWORD WINAPI ThreadFun(LPVOID n)
{
//进入关键区域 (情景:关上物品箱,拨下钥匙)
EnterCreiticalSection (&gBoxKey ) ;(1)
//处理一些不可分割的操作。。。。。
//(情景:转身拿物品,储物品,去购物。。。。)
//离开关键区域 (情景:打开物品箱,拿出储存的物品,插上钥匙)
LeaveCreiticalSection (&gBoxKey) ;(2)
}
void main()
{
//初始化全局锁 (情景:生成物品箱的钥匙)
InitializeCriticalSection( &gBoxKey ) ;
//产生两个线程 (情景:准备两个人抢一个物品箱)
HANDLE hMan1 = CreateThread(NULL,0,ThreadFun,…….);
HANDLE hMan2 = CreateThread(NULL,0,ThreadFun,…….);
CloseHandle(hMan1);
CloseHandle(hMan2);
//删除全局锁 (情景:删除物品箱的钥匙)
DeleteCriticalSection( &gBoxKey ) ;
}
按照上面的程序,我在笔记开头所描述的“超市储物品”的情况是不会发生的,不会再有物品箱被人抢走的可能。由于线程的并发性,从我看到一个空箱子到我将物品的放入物品箱中的这段时间内的操作应该是不可分割的;否则,我看到的物品箱就随时会被人抢走。那么如何保证操作是连续、不可分割呢?简单:看到空箱子,关上箱子,拨下钥匙(1),这时,别人就没办法使用这个箱子了。当我不再需要箱子的时候,打开箱子,插入钥匙(2),就可以让别人使用了。上述就是多线程处理共享资源的一个基本思路。
2 :Mutexes(互斥器)
程序片段:
HANDLE hBoxKey ;
DWORD WINAPI ThreadFun(LPVOID n)
{
//进入关键区域 (情景:关上物品箱,拨下钥匙)
WaitForSingleObject ( hMutex,INFINITE ) ;(1)
//处理一些不可分割的操作。。。。。
//(情景:转身拿物品,储物品,去购物。。。。)
/离开关键区域 (情景:打开物品箱,拿出储存的物品,插上钥匙)
ReleaseMutex ( hMutex ) ;(2)
}
void main()
{
//初始化全局锁 (情景:生成物品箱的钥匙)
hBoxKey = CreateMutex( NULL,FALSE,NULL );
//产生两个线程 (情景:准备两个人抢一个物品箱)
HANDLE hMan1 = CreateThread(NULL,0,ThreadFun,…….);
HANDLE hMan2 = CreateThread(NULL,0,ThreadFun,…….);
CloseHandle(hMan1);
CloseHandle(hMan2);
//删除全局锁 (情景:删除物品箱的钥匙)
CloseHandle( hBoxKey ) ;
}
看到没?无论从共享资源的思路了,还是从程序代码的编制上,使用Mutexes与使用Critical Sections几乎都没有什么区别;但作为 Mutexes机制的提出肯定是有其原因的;我们来看这样的一个情形,当我拿走钥匙以后,因为某些因素再也不能回来了,那么这个箱子便再也不能被使用。也就是说,进入Critical Sections线程若中途当掉了,那么别了线程是再也进不了Critical Sections(一个资源就这样浪费了),那些需要进入Critical Sections的线程会停在入口不再执行,线程永远都结束不了。
怎么办?(用WaitForSingleObject解决)
还记得上一章学过的WaitForSingleObject吗?上一章主要用它等待线程的结束,但这个函数的作用不仅限于此,在这里,我们再前进一小步,探究WaitForSingleObject这个函数的妙用。
原型:
DWORD WaitForSingleObject ( HANDLE hObject ,DWORD time ) ;
hObject:等待对象的handle(代表一个核心对象),在上一章中代表线程handle,注意没有,hObject是代表一个核心对象,也就是说,只要是核心对象,WaitForSingleObject都能处理,处理的原则是“当核心对象被激发时,函数返回”(有多少种核心对象?及核心对象对激发的定义见书P76)。
在这里,我遇到了一个叫mutex核心对象,mutex对激发的定义是:“当没有任何线程拥有该mutex,而且有一个线程正以Wait…()等待该mutex,该mutex就会短暂地出现激发状态,使Wait…()得以返回, 那么在其它的情况,mutex处于未激发状态 ”。
好了,我们又进一步的了解了WaitForSingleObject函数,那么,如何解决Critical Sections所遇到的因难呢?当拥有mutex的线程结束前没有调用ReleaseMutex(不管该线程是当了,还是忘记调用ReleaseMutex),那么其它正以WaitForSingleObject()等待此mutex的线程就会收到WAIT_ABANDONED_0。有了这个值,我就解开难题了。
程序片段:
DWORD rc = WaitForSingleObject( hMutex ) ;
If ( rc == WAIT_OBJECT_0 )
{
//正常的处理
}
else
{
If ( rc == WAIT_ABANDONED_0 )
{
//没有正常的释放mutex(比如拥有mutex的线程死了)
}
}
我想,从笔记的角度来看,上面对mutex描述已经可以帮助我很快的上手使用了,至于对mutex更详细的说明,还是要看看书.。(当然,希望我的这一段描述能帮助你理解mutexJ)
总结一下:
CRITICAL_SECTION Mutex 核心对象
InitializeCriticalSection() CreateMutex()
OpenMutex()
EnterCriticalSection() WaitForSingleObject()
LeaveCriticalSection() ReleaseMutex()
DeleteCriticalSection() CloseHandle()
附注:以上正文纯属加强记忆所做笔记,内容或有不详,甚至错误,请阅者见谅,匆见怪!!!