多线程程序设计.docx

上传人:b****2 文档编号:23181283 上传时间:2023-05-15 格式:DOCX 页数:48 大小:44.50KB
下载 相关 举报
多线程程序设计.docx_第1页
第1页 / 共48页
多线程程序设计.docx_第2页
第2页 / 共48页
多线程程序设计.docx_第3页
第3页 / 共48页
多线程程序设计.docx_第4页
第4页 / 共48页
多线程程序设计.docx_第5页
第5页 / 共48页
点击查看更多>>
下载资源
资源描述

多线程程序设计.docx

《多线程程序设计.docx》由会员分享,可在线阅读,更多相关《多线程程序设计.docx(48页珍藏版)》请在冰豆网上搜索。

多线程程序设计.docx

多线程程序设计

Windows多线程程序设计

在实际的项目开发中,我们或多或少的都接触过多线程程序的设计,有点零星的经验,以我本人的亲身经历而言,对于多线程的编程,我属于“野路子”,有点照猫画虎的意思,“借着”电脑维修之际,系统的学习了一下多线程的编程技术,现将笔记整理如下,希望对有志于学习多线程的同志们有所帮助。

-------haikerenwu

2009-8-1

一.结束线程:

可以利用GetExitCodeThread函数,该函数会传回线程函数的返回值,然而该函数的一个糟糕行为是:

当线程还在进行,尚未有所谓结束代码时,它会传回TRUE表示成功,如果这样第二个形参lpExitCode指向的内存区域中应该放的是STILL_ACTIVE,要注意这种行为,也就是说你不可能从其返回值中知道“到底线程还在运行还是它已结束”,而应根据lpExitCode中是否为STILL_ACTIVE来判断。

For(;;)

{boolrc;rc=GetExitCodeThread(HANDLE,lpExitCode);

If(rc&&(*lpExitCode)!

=STILL_ACTIVE)

//线程结束}

强制结束一个线程可以利用函数voidExitThread(DWORDdwExitCode);形参指定此线程之结束代码,此函数类似于cruntimelibrary中的exit()函数,因为它可以在任何时候被调用并且绝不会返回,任何代码若放在此行之下,保证不会被执行。

程序启动后就执行的那个线程称为主线程,主线程有两个特点,第一,它必须负责GUI程序中的主消息循环;第二,这一线程的结束(不论是因为返回或因为调用了ExitThread)会使得程序中的所有线程都被强迫结束,程序也因此而结束,其他线程没有机会做清理工作。

所以在main或winmain结束之前,应先等待所有的线程都结束。

诊断宏:

#pragmacomment(lib,"USER32")

#include

#defineMTASSERT(a)_ASSERTE(a)

#defineMTVERIFY(a)if(!

(a))PrintError(#a,__FILE__,__LINE__,GetLastError())

__inlinevoidPrintError(LPSTRlinedesc,LPSTRfilename,intlineno,DWORDerrnum)

{

LPSTRlpBuffer;

charerrbuf[256];

#ifdef_WINDOWS

charmodulename[MAX_PATH];

#else//_WINDOWS

DWORDnumread;

#endif//_WINDOWS

FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER

|FORMAT_MESSAGE_FROM_SYSTEM,

NULL,

errnum,

LANG_NEUTRAL,

(LPTSTR)&lpBuffer,

0,

NULL);

wsprintf(errbuf,"\nThefollowingcallfailedatline%din%s:

\n\n"

"%s\n\nReason:

%s\n",lineno,filename,linedesc,lpBuffer);

#ifndef_WINDOWS

WriteFile(GetStdHandle(STD_ERROR_HANDLE),errbuf,strlen(errbuf),&numread,FALSE);

Sleep(3000);

#else

GetModuleFileName(NULL,modulename,MAX_PATH);

MessageBox(NULL,errbuf,modulename,MB_ICONWARNING|MB_OK|MB_TASKMODAL|MB_SETFOREGROUND);

#endif

exit(EXIT_FAILURE);

}

多线程程序设计成功的关键:

(1)各线程的数据要分离开来,避免使用全局变量。

(2)不要在线程之间共享GDI对象

(3)确定你知道你的线程状态,不要径自结束程序而不等待它们的结束。

(4)让主线程处理用户界面。

二.关于Wait…()函数

DWORDWaitForSingleObject(

HANDLEhHandle;

DWORDdwMilliseconds);

参数:

hHandle---等待对象的handle(代表一个核心对象)

dwMilliseconds—等待的最长时间,时间终了,即使handle尚未称为激发状态,此函数还是要返回,此值可以是0(代表立刻返回),也可以是INFINITE代表无穷等待。

返回值:

如果函数失败,则传回WAIT_FAILED,这时候你可调用GetLastError取得更多信息,此函数的成功有三个因素:

1.等待的目标(核心对象)变成激发状态,这种情况下返回值将为WAIT_OBJECT_0.

2.核心对象变成激发状态之前,等待时间终了,这种情况下返回WAIT_TIMEOUT.

3.如果一个拥有mutex(互斥器)的线程结束前没有释放mutex,则传回WAIT_ABANDONED.

获得一个线程对象的handle之后,WaitForSingleObject要求操作系统让线程1睡觉,直到以下任何一种情况发生:

1.线程2结束

2.dwMilliseconds时间终了,该值系从函数调用后开始计算。

由于操作系统追踪线程2,所以即使线程2失事或被强迫终止,该函数也能正常工作。

关于该函数的第二个参数,若设定为0,可使你能够检查handle的状态并立刻返回,没有片刻停留,如果handle已经备妥,那么这个函数会成功并传回WAIT_OBJECT_0,否则,这个函数立刻返回并传回WAIT_TIMEOUT.

可被WaitForSingleObject使用的核心对象有两种状态:

激发与未激发。

Wait函数会在目标变成激发状态时返回。

当线程正在执行时,线程对象处于未激发状态,当线程结束,线程对象就被激发了,因此,任何线程如果等待的是一个线程对象,将会在等待对象结束时被调用,因为当时线程对象自动变成激发状态。

Win32的核心对象激发状态的意义

对象

说明

Thread

当线程结束时,线程对象即被激发,当线程还在进行时,则对象处于未激发状态,线程对象由CreateThread或CreateRemoteThread产生

Process

当进程结束时,进程对象即被激发,当进程还在进行时,则对象处于未激发状态,CreateProcess或OpenProcess会传回一个进程对象的handle

ChangeNotification

当一个特定的磁盘子目录中发生一件特别的变化时,此对象即被激发,此对象系由FindFirstChangeNotification产生

ConsoleInput

当console窗口的输入缓冲区中有数据可用时,此对象处于激发状态,CreateFile或GetStdFile两函数可以获得consolehandle.

Event

Event对象的状态直接受控于应用程序所使用的三个Win32函数:

SetEvent(),

PulseEvent,RestEvent。

CreateEvent或OpenEvent都可以传回一个handle,Event对象的状态可被操作系统设定---如果使用于overlapped操作时。

Mutex

如果mutex没有被任何线程所拥有,它就是处于激发状态,一旦一个等待mutex的函数返回了,mutex也就自动重置为未激发状态,CreateMutex或OpenMutex都可以获得一个mutexhandle。

Semaphore

Semaphore有点像mutex,但它有个计数器,可以约束其拥有者(线程)的个数,当计数器大于0时,Semaphore处于激发状态,当计数器等于0时,semaphore处于未激发状态,CreateSemaphore或OpenSemaphore可以传回一个semaphorehandle。

WaitForMultipleObject函数:

DWORDWaitForMultipleObject(

DWORDnCount,

CONSTHANDLE*lpHandles,

BOOLbWaitAll,

DWORDdwMillisencods)

参数:

nCount—表示lpHandles所指之handles数组的元素个数,最大容量为MAXIMUM_WAIT_OBJECTS.

lpHandles—指向一个由对象handles所组成的数组,这些handles不需要为相同的类型。

bWaitAll—如果此为true,表示所有的handles都必须激发,此函数才得以返回,否则此函数将在任何一个handle激发时返回。

dwMilliseconds—当该事件长度终了时,即使没有任何handles激发,此函数也会返回,此值可为0,以便测试,亦可指定INFINITE,表示无穷等待。

返回值:

1.如果因时间终了而返回,则返回值是WAIT_TIMEOUT.

2.如果bWaitAll是TRUE,那么返回值将是WAIT_OBJECT_0.

3.如果bWaitAll是FALSE,那么返回值减去WAIT_OBJECT_0,就表示数组中的哪一个handle被激发了。

4.如果你等待的对象中有任何mutexes,那么返回值可能从WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1.

5.如果函数失败,它会传回WAIT_FAILED,这时候你可以用GetLastError找出失败的原因。

GetMessage函数等待消息而不是核心对象,一旦你调用GetMessage,除非有一个消息真正进入你的消息队列,否则它不会返回,在此期间,Windows就可以自由地将CPU时间给与其他程序。

如果你正使用WaitSingleObject或WaitForMultipleObjects等待某个对象被激发,你根本没有办法回到主消息循环中去。

为解决这个问题,主消息循环必须修改,使它得以同时等待消息或核心对象被激发,必须使用一个MsgWaitForMultipleObjects函数,这个函数非常类似WaitForMultipleObjects,但它会在“对象被激发”或“消息到达队列”时被唤醒而返回。

DWORDMsgWaitForMultipleObjects(

DWORDnCount,

LPHANDLElpHandles,

BOOLfWaitAll,

DWORDdwMilliseconds,

DWORDdwWakeMask);

参数:

dwWakeMask—欲观察的用户输入消息,可以是:

QS_ALLINPUT,QS_HOTKEY,QS_INPUT,QS_KEY,QS_MOUSE,QS_MOUSEBUTTON,QS_MOUSEMOVE,QS_PAINT,QS_POSTMESSAGE,QS_SENDMESSAGE,QS_TIMER.

返回值:

和WaitForMultipleObjects相比较,MsgWaitForMultipleObjects有一些额外的返回值意义,为了表示“消息到达队列”,返回值将是WAIT_OBJECT_0+nCount。

while(!

quit||gNumPrinting>0)

{

DWORDdwWake;

dwWake=MsgWaitForMultipleObjects(gNumPrintings,gPrintJobs,FALSE,INFINITE,QS_ALLEVENTS);

if(dwWake>=WAIT_OBJECT_0&&dwWake

{

//处理有信号的核心对象

}

elseif(dwWake==WAIT_OBJECT_0+gNumPrinting)

{

While(PeekMessage(&msg,NULL,0,0,PM_REMOVE))

{

If(hDlgMain==NULL||!

IsDialogMessage(hDlgMain,&msg))

{

if(msg.message==WM_QUIT)

{

quit=TRUE;

exitcode=msg.wParam;

break;

}

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}//endwhile

}

}//endwhile

有数种情况是这个循环必须处理而却可能在它第一次设计时容易被忽略的:

1.在你收到WM_QUIT之后,Windows仍然会传送消息给你,如果你要在收到WM_QUIT之后等待所有线程结束,你必须继续处理你的消息,否则窗口会变得反应迟钝,而且没有重绘能力。

2.该函数不允许handles数组中有缝隙产生。

所以当某个handle被激发了时,你应该在下一次调用该函数之前先把handles数组做个整理,紧压,不要只是把数组中的handle设为NULL。

3.如果有另一个线程更改了对象数组,而那是你正在等待的,那么你需要一种新方法,可以强迫MsgWaitForMultipleObjects返回,并重新开始,以包含这个新的handle。

三.同步控制

当线程1调用线程2时,线程1停下不动,直到线程2完成返回到线程1来,线程1才继续下去,这就是所谓的同步(synchronous),如果线程1调用线程2后,径自继续自己的下一个动作,那么两者之间就是所谓的异步(asynchronous),例如SendMessage就是同步行为,而PostMessage属于异步行为。

1.临界区:

criticalsection并不是核心对象,因此,没有所谓的handle这样的东西,它和核心对象不同,它存在于进程的内存空间中,你不需要使用像“create”这样的api函数来获得一个criticalsectionhandle,你应该做的是将一个类型为CRITICAL_SECTION的局部变量初始化,方法是调用InitializeCriticalSection;

VOIDIniitializeCriticalSection(

LPCRITICAL_SECTIONlpCriticalSection),当利用毕cirticalsection时,你必须调用DeleteCriticalSection清除它,但这个函数并没有“释放对象”的意义在里头。

一旦criticalsection被初始化,每一个线程就可以进入其中—只要它通过了EnterCriticalSection这一关。

当线程准备好离开cirticalsection时,它必须调用LeaveCriticalSection。

一旦一个线程进入一个criticalsection,它就能够一再地重复进入该criticalsection,但是每一个“进入”操作都必须有一个对应的“离开”操作。

千万不要在criticalsection之中调用Sleep或任何的Wait..函数。

由于criticalsection不是核心对象,如果进入criticalsection的那个线程结束了或当掉了,而没有调用LeaveCriticalSection的话,系统没有办法将criticalsection清除,如果你需要那样的机能,你应该使用mutex。

在WindowsNT之中,如果一个线程进入某个criticalsection而在未离开的情况下就结束,该criticalsection会被永远锁住,而在Windows95中,如果发生同样的情况,其他等着要进入该cirticalsection的线程,将获准进入,这基本上是一个严重的问题,因为你竟然可以在你的程序处于不稳定状态时进入该criticalsection。

2.互斥体(mutex):

mutex和criticalsection做相同的事情,但是它们的运作还是有差别的。

(1)锁住一个未被拥有的mutex,比锁住一个未被拥有的criticalsection,需要花费几乎100倍的时间,因为criticalsection不需要进入操作系统的内核,直接在ring3级就可以进行操作。

(2)Mutexes可以跨进程使用,Criticalsection则只能在同一个进程中使用。

(3)等待一个mutex时,你可以指定“结束等待”的时间长度,但对于criticalsection则不行。

为了能够跨进程使用同一个mutex,你可以在产生mutex时指定其名称,这个名称对整个系统而言是全局性的,所以应保证其独一无二性,如果你指定了名称,系统中的其他任何线程就可以使用这个名称来处理该mutex,一定要使用名称,因为你没有办法把handle交给一个执行中的进程。

Mutex是一个核心对象,因此它被保持在系统核心之中,并且和其他核心对象一样,有所谓的引用计数。

利用CreateMutex函数产生一个mutex。

HANDLECreateMutex(

LPSECURITY_ATTRIBUTESlpMutexAttributes,

BOOLbInitialOwner,

LPCTSTRlpName)

lpMutexAttributes—安全属性,NULL表示使用默认的属性,这一指定在Win95中无效。

bInitialOwner—如果你希望“调用CreateMutex的这个线程”拥有产生出来的mutex,就将此值设为TRUE.

lpName—mutex的名称,任何进程或线程都可以根据此名称使用这一mutex,名称可以是任意字符串,只要不含\即可。

返回值:

如果成功,则传回mutex的handle,否则传回NULL,调用GetLastError可以获得进一步的信息,如果指定mutex的名称已经存在,GetLastError会传回ERROR_ALREADY_EXISTS.

当不再需要一个mutex时,可调用CloseHandle将它关闭,和其他核心对象一样,mutex有一个引用计数,每次调用CloseHandle,引用计数便减1,当引用计数为0时,mutex便自动被系统销毁。

打开一个应经存在的mutex,可调用函数OpenMutex,如调用CreateMutex建立一个已经存在的mutex,会传回该mutexhandle,但是GetLastError会传回ERROR_ALREADY_EXISTS.

欲获得一个mutex的拥有权,使用Win32的Wait。

()函数,如果没有任何线程拥有那个mutex,Wait。

函数就会成功。

Mutex的拥有权并非属于那个产生它的线程,而是那个最后对此Mutex进行Wait。

操作并且尚未进行ReleaseMutex操作的线程。

在一个适当的程序中,线程绝对不应该在它即将结束前还拥有一个mutex,因为这意味着线程没有能够适当地清除其资源,为了解决这个问题,mutex有一个非常重要的特性,这性质在各种同步机制中是独一无二的。

如果线程拥有一个mutex而在结束前没有调用ReleaseMutex,mutex不会被摧毁,取而代之,该mutex会被视为“未被拥有”以及“未被激发”,而下一个等待中的线程会被以WAIT_ABANDONED_0通知,不论线程是因为ExitThread而结束,或是因当掉而结束,这种情况都存在。

如果其他线程正以WaitForMultipleObjects等待此mutex,该函数也会返回,传回值介于WAIT_OBJECT_0和WAIT_OBJECT_n+1之间,其中n为handle数组的元素个数,线程可以根据这个值了解到究竟哪一个mutex被放弃了,至于WaitForSingleObject则只是传回WAIT_ABANDONED_0.

CreateMutex的第二个参数允许你指定现行线程是否立刻拥有即将产生出来的mutex。

是为了阻止跨进程使用时导致的racecondition的发生。

3.要在Win32环境中产生一个semaphore,必须使用CreateSemaphore函数。

HANDLECreateSemaphore(

LPSECURITY_ATTRIBUTESlpAttributes,

LONGlInitialCount,

LONGlMaximumCount,

LpCTSTRlpName)

参数:

lpAttributes----安全属性,如果是NULL,就表示要使用默认属性,Win95忽略这一参数。

lInitialCount---semaphore的初值,必须大于或等于0,并且小于或等于lMaximumCount。

lMaximumCount---semaphore的最大值,这也就是在同一时间内能够锁住semaphore之线程的最多个数。

lpName---semaphore的名称,任何线程或进程都可以根据这一名称引用到这个semaphore,这个值可以是NULL,意思是产生一个没有名字的semaphore。

返回值:

如果成功就传回一个handle,否则传回NULL,不论哪一种情况GetLastError都会传回一个合理的结果,如果指定的semaphore名称已经存在,则该函数还是成功,GetLastError会传回ERROR_ALREADY_EXISTS。

如果锁定成功(利用wait函数),与mutex不同的是,你并不会收到semaphore的拥有权,因为可以有一个以上的线程同时锁定一个semaphore,所以谈semaphore的拥有权并没有太多的意义。

因为没有拥有权这种概念,一个线程可以反复调用Wait函数以产生新的锁定,这和mutex不同,拥有mutex的线程不论再调用多少次wait函数,也不会被阻塞住。

BOOLReleaseSemaphore(HANDLEhSemaphore,LONGlReleaseCount,LPLONGlpPreviousCount)

参数:

hSemaphore----Semaphore的handle

lReleaseCount—Semaphore现值的增额,该值不可以是负值或0

lpPreviousCount—借此传回semaphore原来的值。

返回值:

如果成功,则传回TRUE,否则传回FALSE,失败时可调用GetLastError获得原因。

ReleaseSemaphore对于semaphore所造成的现值的增加绝对不会超过CreateSemaphore时所指定的lMaximumCount。

注意lpPreviousCount所传回来的是一个瞬间值,你不可以把lReleaseCount加上*lpPreviousCount就当做是semaphore的现值,因为其他线程可能已经改变了semaphore的值。

同时与mutex不同的是,调用ReleaseSemaphore的那个线程并不一定就得是调用wait的那个线程,任何线程都可以在任何时间调用ReleaseSemaphore,解除被任何线程锁定的sema

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 小学作文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1