1、第四章 Windows多线程编程及调优,浙江大学计算机学院,Windows多线程API,在Windows平台下可以通过Windows的线程库来实现多线程编程提供的接口Win32 API或MFC.Net Framework实现方式的多样化给Windows编程带来了很大的灵活性也使得多线程编程变得复杂,Windows线程库,Win32 APIWindows操作系统为内核以及应用程序之间提供的接口将内核提供的功能进行函数封装应用程序通过调用相关的函数获得相应的系统功能。MFC微软基础函数类库(Microsoft Foundation Classes)用类库的方式将Win32 API 进行封装,以类的
2、方式提供.NETFramework构成公共语言运行库(CommonLanguageRuntime,CLR)文件加载器、垃圾收集器、安全系统Framework类库(FrameworkClassLibrary,FCL).NET 基础类库的System.Threading命名空间提供了大量的类和接口来支持多线程所有与多线程机制相关的类都存放在System.Threading命名空间中,使用win32线程API,Win32 函数库中提供了操作多线程的函数,包括创建线程、管理线程、终止线程、线程同步等接口。提供操作系统级别的创建线程的操作线程函数DWORD WINAPI ThreadFunc(LPVOI
3、D lpvThreadParm);线程创建HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,SIZE_T dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);,第一个参数lpThreadAtt,是一个指向SECURITY-ATTRIBUTES结构的指针,该结构制定了线程的安全属性,缺省为 NULL。第二个参数dwStackSize,是栈的大小,一般设置为0
4、。第三个参数LPTHREAD_START_ROUTINE是新线程开始执行时,线程函数的入口地址。它必须是将要被新线程执行的函数地址,不能为NULL。lpStartAddress 参数指定了线程函数的地址,新建线程将从此地址开始执行,直到return语句返回,线程运行结束,把控制权交给操作系统。第四个参数lpParameter,是线程函数定义的参数。可以通过这个参数传送值,包括指针或者NULL。第五个参数dwCreationFlags,控制线程创建的附加标志,可以设置两种值。0表示线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,
5、并不马上执行,直至函数ResumeThread被调用;第六个参数lpThreadID,为指向32位变量的指针,该参数接受所创建线程的ID号。如果创建成功则返回线程的句柄,否则返回NULL。,_beginthread,创建线程还可以用process.h头文件中声明的C执行时期链接库函数_beginthread。在回调入口函数之前进行一些线程相关的CRT的初始化操作语法:hThread=_beginthread(void(_cdecl*start_address)(void*),unsigned stack_size,void*arglist);线程函数的语法:void _cdecl Thread
6、Proc(void*pParam);,使用_beginthread创建线程的例子,#include stdafx.h#include#include#include#include using namespace std;void ThreadFunc1(PVOID param)while(1)Sleep(1000);coutThis is ThreadFunc1endl;void ThreadFunc2(PVOID param)while(1),Sleep(1000);coutThis is ThreadFunc2endl;int main()int i=0;_beginthread(Thr
7、eadFunc1,0,NULL);_beginthread(ThreadFunc2,0,NULL);Sleep(3000);coutendendl;return 0;,线程管理,设置线程的优先级一个线程被创建时,它的优先级等于它所属进程的优先级SetThreadPriority函数设置线程的相对优先级Bool SetThreadPriority(HANDLE hPriority,int nPriority)线程与包含它的进程的优先级关系如下:线程优先级=进程优先级+线程相对优先级,设置线程的优先级,参数hPriority 是指向待设置的线程句柄进程的优先级包括:实时:REALTIME_PRIO
8、RITY_CLASS;高:HIGH _PRIORITY_CLASS;高于正常:ABOVE_NORMAL_PRIORITY_CLASS;正常:NORMAL _PRIORITY_CLASS;低于正常:BELOW_ NORMAL _PRIORITY_CLASS;空闲:IDLE_PRIORITY_CLASS。nPriority 是线程的相对优先级,可以是以下的值:最高线程:THREAD-PRIORITY-HIGHEST-2空闲:THREAD-PRIORITY-IDLE 15最低线程:THREAD-PRIORITY-LOWEST2低于正常线程:THREAD-PRIORITY-BELOW-NORMAL 1
9、正常线程:THREAD-PRIORITY-NORMAL 0高于正常线程:THREAD-PRIORITY-ABOVE NORMAL-1关键时间:THREAD-PRIORITY-TIME CRITICAL-15,线程的挂起与恢复,进程中的每个线程都有挂起计数器(suspend count)当挂起计数器值为0 时,线程被执行当挂起计数器值大于0 时,调度器不去调度该线程。不能够直接访问线程的挂起计数器 挂起 DWORD SuspendThread(HANDLE hThread);恢复 DWORD ResumeThread(HANDLE hThread);原型DWORD SuspendThread(H
10、ANDLE hThread);挂起指定的线程如果函数执行成功,则线程的执行被终止每次调用SuspendThread()函数,线程将挂起计数器的值增1 DWORD ResumeThread(HANDLE hThread);结束线程的挂起状态来执行这个线程每次调用ResumeThread()函数,线程将挂起计数器的值减1若挂起计数器的值为0,则不会再减,线程等待,一组能使线程阻塞其自身执行的等待函数WaitForSingleObjectWaitForMultipleObject在其参数中的一个或多个同步对象产生了信号,或者超过规定的等待时间才会返回在等待函数未返回时,线程处于等待状态,线程只消耗很
11、少的CPU时间,线程终结,在线程函数返回时,线程自动终止在线程的执行过程中终止则可调用函数:VOID ExitThread(DWORD dwExitCode);deallocate栈;取消I/O;结束线程如果在线程的外面终止线程,则可调用下面的函数:BOOL TerminateThread(HANDLE hThread,DWORDdw ExitCode);,Win32多线程的实现,下面这个程序首先创建两个线程,当输入为1时,执行线程,否则挂起线程。,#include#include using namespace std;DWORD WINAPI FunOne(LPVOID param)whi
12、le(true)Sleep(1000);couthello!;,return 0;DWORD WINAPI FunTwo(LPVOID param)while(true)Sleep(1000);coutworld!;return 0;,Win32多线程的实现(续),int main(int argc,char*argv)int input=0;HANDLE hand1=CreateThread(NULL,0,FunOne,(void*),线程执行和资源存取,线程之间通信的两个基本问题是互斥和同步线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程
13、的消息时应该等待,直到消息到达时才被唤醒。线程互斥是指对于共享资源,在各线程访问时的排它性线程互斥是一种特殊的线程同步 Win32线程同步的实现全局变量事件(Event)临界区(Critical section)互斥量(Mutex)信号量(Semaphore),Win32线程同步的实现-全局变量,全局变量进程中的所有线程均可以访问所有的全局变量,因而全局变量成为Win32多线程通信的最简单方式。int var;/全局变量UINT ThreadFunction(LPVOID pParam)while(var)/线程处理return 0;var是一个全局变量,任何线程均可以访问和修改。线程间可以利
14、用此特性达到线程同步的目的。,用全局变量同步线程的例子,#include#include using namespace std;int globalvar=false;DWORD WINAPI ThreadFunc(LPVOID pParam)coutThreadFuncendl;Sleep(200);globalvar=true;return 0;问题:主线程等待globalvar为真,如不为真则一直循环,这样占用了CPU资源如果主线程优先级高于ThreadFunc,则globalvar一直不会被置为真,int main()HANDLE hthread=CreateThread(NULL,
15、0,ThreadFunc,NULL,0,NULL);if(!hthread)coutThread Create Error!endl;CloseHandle(hthread);while(!globalvar)coutThread whileendl;coutThread exitendl;return 0;,事件,事件(Event)是WIN32提供的最灵活的线程间同步方式用来发送命令或触发事件,安排事件执行的先后次序事件存在两种状态:激发状态(signaled or true)未激发状态(unsignal or false)事件可分为两类:手动设置这种对象只能用程序来手动设置,在需要该事件或
16、者事件发生时,采用SetEvent及ResetEvent来进行设置。自动恢复一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态,原型为:HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState,LPCTSTR lpName);其中:第一个参数:与Creat
17、eThread中的第一个参数类似,是一个指向SECURITY-ATTRIBUTES结构的指针第二个参数:代表事件的类型,是手动清除事件信号还是自动清除事件信号。TRUE为手动清除。第三个参数:指明事件的初始状态第四个参数:事件的名称,使用“事件”机制注意事项,(1)设置事件是否要自动恢复;(2)设置事件的初始状态;(3)如果跨进程访问事件,必须对事件命名,在对事件命名的时候,要注意不要与系统命名空间中的其它全局命名对象冲突事件对象属于内核对象进程A可以通过调用OpenEvent函数根据对象的名字获得进程B中event对象的句柄,然后对这个句柄可以使用ResetEvent、SetEvent和Wa
18、itForMultipleObjects等函数进行操作来实现一个进程的线程控制另一进程中线程的运行 当程序中一个线程的运行要等待另外一个线程中一项特定的操作的完成才能继续执行时,就可以使用事件对象来通知等待线程某个条件已满足,事件机制例子,有三个线程主线程、读线程ReadThread、写线程WriteThread 读线程ReadThread必须在写线程WriteThread 的写操作完成之后才能进行读操作主线程必须在读线程ReadThread 的读操作完成后才结束定义两个事件对象evRead,evFinishevRead由写线程WriteThread用于通知读线程ReadThread 进行读操
19、作evFinish由读线程ReadThread用于通知主线程读操作已经结束,#include stdafx.h#include#include#include#include using namespace std;HANDLE evRead,evFinish;void ReadThread(LPVOID param)WaitForSingleObject(evRead,INFINITE);coutReadingendl;SetEvent(evFinish);程序输出如下:WritingReading.The Program is EndWaitForSingleObject Waits un
20、til the specified object is in the signaled state or the time-out interval elapses,void WriteThread(LPVOID param)coutWritingendl;SetEvent(evRead);int main(int argc,char*argv)evRead=CreateEvent(NULL,FALSE,FALSE,NULL);evFin=CreateEvent(NULL,FALSE,FALSE,NULL);_beginthread(ReadThread,0,NULL);_beginthrea
21、d(WriteThread,0,NULL);WaitForSingleObject(evFinish,INFINITE);coutThe Program is Endendl;return 0;,临界区,一种防止多个线程同时执行一个特定代码段的机制适用于多个线程操作之间没有先后顺序但要求互斥的同步多个线程访问同一个临界区的原则:一次最多只能一个线程停留在临界区内 不能让一个线程无限地停留在临界区内,否则其它线程将不能进入该临界区定义临界区变量的方法如下:CRITICAL_SECTION gCriticalSection;通常情况下,CRITICAL_SECTION结构体应该被定义为全局变量,便
22、于进程中的所有线程可以方便地按照变量名来引用该结构体。,CS相关API,初始化临界区VOID WINAPI InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);删除临界区VOID WINAPI DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);进入临界区VOID WINAPI EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);离开临界区VOID WINAPI LeaveCritica
23、lSection(LPCRITICAL_SECTION lpCriticalSection);,使用临界区编程的一般方法是:void WriteData()EnterCriticalSection(例子假如一个银行系统有两个线程执行取款任务,一个使用存折在柜台取款,一个使用银行卡在ATM取款。若不加控制,很可能账户余额不足两次取款的总额,但还可以把钱取走。,#include stdafx.h#include#include#include#include using namespace std;int total=100;HANDLE evFin2;CRITICAL_SECTION cs;vo
24、id WithdrawThread1(LPVOID param)EnterCriticalSection(if(total-20=0),total-=20;coutYou withdraw 20endl;elsecoutYou do not have that much moneyendl;LeaveCriticalSection(,互斥量,通常用于协调多个线程或进程的活动,通过“锁定”和“取消锁定”资源,控制对共享资源的访问。互斥量的作用是保证每次只能有一个线程获得互斥量使用CreateMutex函数创建:HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lp
25、MutexAttributes,/安全属性,如果为0,则不可继承BOOL bInitialOwner,LPCTSTR lpName);,有关的API:CreateMutex 创建一个互斥对象,返回对象句柄;OpenMutex 打开并返回一个已存在的互斥对象的句柄,使之后续访问;ReleaseMutex 释放对互斥对象的占用,使之成为可用;使用互斥量的一般方法是:void Writedata()WaitForSingleObject(hMutex,);./do somethingReleaseMutex(hMutex);,例子,#include stdafx.h#include#include#
26、define THREAD_INSTANCE_NUMBER3LONG g_fResourceInUse=FALSE;LONG g_lCounter=0;DWORD ThreadProc(void*pData)int ThreadNumberTemp=(*(int*)pData);HANDLE hMutex;cout ThreadProc:ThreadNumberTemp is running!endl;if(hMutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,Mutex.Test)=NULL)cout Open Mutex error!endl;cout Threa
27、dProc ThreadNumberTemp gets the mutex endl;ReleaseMutex(hMutex);CloseHandle(hMutex);return 0;,续1,int main(int argc,char*argv)int i;DWORD IDTHREAD_INSTANCE_NUMBER;HANDLE hTHREAD_INSTANCE_NUMBER;HANDLE hMutex;if(hMutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,Mutex.Test)=NULL)if(hMutex=CreateMutex(NULL,FALSE,
28、Mutex.Test)=NULL)cout Create Mutex error!endl;return 0;for(i=0;iTHREAD_INSTANCE_NUMBER;i+)hi=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,(void*),续2,if(hi=NULL)cout CreateThread error IDi endl;elsecout CreateThread:IDi endl;WaitForMultipleObjects(THREAD_INSTANCE_NUMBER,h,TRUE,INFINITE);cou
29、t Close the Mutex Handle!endl;CloseHandle(hMutex);return 0;,程序运行结果:CreateThread:1796ThreadProc:1796 is running!ThreadProc 1796 gets the mutexCreateThread:2140ThreadProc:2140 is running!ThreadProc 2140 gets the mutexCreateThread:2448ThreadProc:2448 is running!ThreadProc 2448 gets the mutexClose the M
30、utex Handle!,信号量,信号量是一个核心对象,拥有一个计数器,可用来管理大量有限的系统资源当计数值大于零时,信号量为有信号状态当计数值为零时,信号量处于无信号状态创建信号量HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa,LONG lInitialCount,LONG lMaximumCount,PCTSTR pszName/信号量的名称);释放信号量BOOL WINAPI ReleaseSemaphore(HANDLE hSemaphore,LONG lReleaseCount,/信号量的当前资源数增加lReleaseCountLPLON
31、G lpPreviousCount);打开信号量HANDLE OpenSemaphore(DWORD fdwAccess,/The access to the semaphore objectBOOL bInherithandle,/If this value is TRUE,processes created by this process will inherit the handle.Otherwise,the processes do not inherit this handle.PCTSTR pszName);,使用信号量机制同步线程 例子,#include stdafx.h#in
32、clude#include#define THREAD_INSTANCE_NUMBER3DWORD foo(void*pData)int ThreadNumberTemp=(*(int*)pData);HANDLE hSemaphore;cout foo:ThreadNumberTemp is running!endl;if(hSemaphore=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,Semaphore.Test)=NULL)cout Open Semaphore error!endl;cout foo ThreadNumberTemp gets t
33、he semaphore endl;ReleaseSemaphore(hSemaphore,1,NULL);CloseHandle(hSemaphore);return 0;,例子续,int main(int argc,char*argv)int i;DWORD ThreadIDTHREAD_INSTANCE_NUMBER;HANDLE hThreadTHREAD_INSTANCE_NUMBER;HANDLE hSemaphore;if(hSemaphore=CreateSemaphore(NULL,0,1,Semaphore.Test)=NULL)cout Create Semaphore error!endl;return 0;for(i=0;iTHREAD_INSTANCE_NUMBER;i+)hThreadi=CreateThread(NULL,0,(LPTHREAD_START_
copyright@ 2008-2023 冰点文库 网站版权所有
经营许可证编号:鄂ICP备19020893号-2