《WIN32多线程程序设计》这本书也看了好多遍,但总是觉得印象不深;我想是不是应该做做笔记,一来可以加深一下印象。二也在可以在用时方便查阅。J
首先,我觉得线程一点都不难(这样的心理暗示会使我觉得学的轻松一些);
why?
与调用函数的过程类似;线程只不过用CreateThread的API将函数
HANDLE hThread = CreateThread (
LPSECURITY_ATTRIBUTES lpThreadAtt,
DWORD dwStackSize
LPTHREAD_START_ROUTINE lpFun,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadID)
LpFun:就是指向被封装的函数的地址。
LpParameter: 就是指向被封装的函数的参数(没有参数填入null)
图示:
CreateThread()除了LpFun,LpParameter两个参数极为重要外,它的其它的参数有啥用的?当然的有用了,具体使用的时候查帮助就行了,实在不会,那就设为null得了。
当调用CreateThread()后,会产生线程核心对象(如果已经产生了线程核心对象,那么就只将此核心对象的引用计数加1;另外,win32系统中有若干种核心对象),并以句柄的方式代表它。那么,显然,在线程使用完毕后就应该用CloseHandle()函数关闭。
既然线程是独立的运行的程序,那么主程序如何知道线程执行完毕呢?答案是通过GetExitCodeThread(HANDLE hThread,LPDWORD lpExitCode)函数。如果线程已结束,那么线程结束的代码被放在lpExitCode参数中带回来,如果线程尚未结束,lpExitCode带回来的值是STILL_ACTIVE。
值得注意的是:GetExitCodeThread(HANDLE hThread,LPDWORD lpExitCode)还可以带回线程函数的返回值。lpExitCode就是函数的返回值
例:
线程函数:
DWORD WINAPI ThreadFun(LPVOID n)
{
return 10;
}
int main()
{
hThrd = CreateThread(null,ThreadFun,…..);
DWORD exitCode=0;
for( ; ; )
{
GetExitCodeThread(hThrd,&exitCode);
If ( exitCode == STILL_ACTIVE)
{
//线程仍然在运行
}
else
{
break;
}
}
//exitCode保存了函数的返回值10
printf(“线程函数的返回值: %d /n”, exitCode);
CloseHandle(hThrd);
Return 0;
}
这一章节的内容大概就是这么多,总结一下:
* 产生一个线程 [ CreateThread() ]
* 判断线程是否结束 [ GetExitCodeThread () ]
* 关闭线程 [ CloseHandle () ]
* 强制退出线程 [ ExitThread () ]
WIN32多线程程序设计学习笔记(第三章)
通过在上一章的学习,我学会使用GetExitCodeThread()这个函数来判断一个线程是否还在执行。通过这个函数,我就可以对付“某个线程必须在某个线程结束后才能运行”的情况。复习一下:
程序片段:
for ( ; ; )
{
int rc;
rc = GetExitCodeThread(hThrd,&exitCode);
if (!rc && exitCode != STILL_ACTIVE)
break;
}
使用这个方法,必须持续不断的调用GetExitCodeThread(),直到其结果不再是STILL_ACTIVE。书上说这方法不好,很浪费cpu时间.,称为忙等待(busy waits)。忙等待必须应有的重视,不然你会发现当包含忙等待的程序运行起来后,其它正在进行的程序的反应就很迟钝了。因为cpu几乎用了所有的可用时间去检查线程是否结束。
那么,我应该怎么做呢?如何才更有效率呢?
另一种有效率的方法:
使用WaitForSingleObject()这个函数来完美的解决问题。
完成上面程序片段同样功能只需一句话:
WaitForSingleObject ( hThrd, INFINITE );
若hThrd线程没有结束时,调用这个函数的程序就会在函数调用点停下来,直到hThrd线程结束时(术语:hThrd被激发)才继续向下执行。
不知道大家会不会有这样疑问------这个函数不会只是把上面的程序片段包装了一下吧?它真是很有效率吗?如果它有效率,它是如何做到的呢?
可以告诉大家,WaitForSingleObject()不是上述程序片段的简单包装,它真的很有效率(不信,按书上的方法,打开性能管理器看看),至于它如何做到的,我也有兴趣知道(知道告诉我),我只知道这个函数得到了操作系统级的某些底层调度函数的支持。
基本上本章的核心内容就是以上所述了。(至于WaitForSingleObject()参数及返回值的说明,查书就行了,毋需多言)
具体的看一个实在的例子,胜过在此长篇大论,下面请看:
//参考书上例子
//程序的目的:只用3个线程,完成6件事
int main()
{
HANDLE hThrds[3];
int slot = 0;
For ( int I=1 ; I<=6 ; I++)
{
if( I > 3 )
{//已经存在3个线程了
//等待其中的一个线程线束后,再创建线程做剩余的事情
//效率不高,因为线程结束的次序与它们被产生的次序不同
WaitForSingleObject( hThrds[slot] , INFINITE ) ;
CloseHandle ( hThrds[slot] );
}
//构造线程
hThrds[slot] = CreateThread(NULL,0,ThreadFunc,NULL,0,NULL);
if (++slot>2)
slot=0;
}// end for
for ( slot=0; slot<3; slot++)
{
//等待剩余的线程结束
WaitForSingleObject( hThrds[slot] , INFINITE ) ;
CloseHandle ( hThrds[slot] );
}// end for
}
上面的这段程序有一个问题,就是效率不是很高;理想的状况是,一旦有一个线程结束,就立刻产生一个线程补上。仔细读上面的程序,你会发现它无法实现理想的状况;因为它假设线程结束的次序会和它们被产生的次序相同。比如,当依序产生了1,2,3线程以后,一定是按1,2,3的次序结束。其实不然,也许2比1更早结束。在这时,上面的程序并不会立刻产生一个线程填补2,而是非要等1结束了,才产生线程。那么能够实现理想的状况吗?答案是可以,请用WaitForMultipleObjects() 函数, 这个函数的用法基本上与WaitForSingleObject()差不多,在这里我就不举例说明了。
本章最后还提到了MsgWaitForMultipleObjects() , 这个函数特点是在消息到达或线程结束时返回。
我想,只要用会了WaitForSingleObject()这个函数, 那么WaitForMultipleObjects()和MsgWaitForMultipleObjects()看一下也就会用了(书上举了两个小例子来说明,看一下花不了多少时间,不必我在这里废话了)。
这一章节的内容大概就是这么多,总结一下:
* 等待线程结束 [ WaitForSingleObject() ]
* 等待多个线程结束 [ WaitForMultipleObjects() ]
* 等待多个线程结束或消息到达 [ MsgWaitForMultipleObjects() ]
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()
封装起来,并产生一个与主程序同时执行的程序来调用被封装的函数。