为什么要进行线程同步 在多线程的程序中,很少有多个线程能在其生命期内进行完全独立的操作;通常情况是一些线程进行某些操作,而其他的线程必须对其操作后的结果进行了解。如果不采取同步机制,其他线程会在线程处理任务前访问处理结果,这样会产生错误的了解。例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题;若一个线程负责写操作,其他线程负责读取操作,则不能保证读取的就是修改过的值,这时就必须在变量写操作过程时加上访问限制,在写操作完成后解除访问限制。这种保证线程能正确获取其他线程处理结束后的结果的措施称为线程同步。 线程同步的四种方式: 同步方式 速度、资源开销 跨进程 资源统计 CriticalSection 速度快、非内核对象 不能用于不同进程 不能资源统计(每次只能有一个线程对共享资源进行存取) Mutex 速度慢,内核对象 可用于不同进程 不能资源统计 Semaphore 速度慢、内核对象 可用于不同进程 可资源统计(可以让一个或多个线程对共享资源进行存取) Event 速度慢、内核对象 可用于不同进程 可资源统计CriticalSection 临界区(CriticalSection):通过对多线程的串行化来访问公共资源或一段代码,本身不是内核对象,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开。临界区被释放后,其他线程才可以抢占。 【初始化临界区】VOIDWINAPIInitializeCriticalSection(LPCRITICALSECTIONlpCriticalSection); 【删除临界区】VOIDWINAPIDeleteCriticalSection(LPCRITICALSECTIONlpCriticalSection); 【获取临界区】VOIDWINAPIEnterCriticalSection(LPCRITICALSECTIONlpCriticalSection); 【释放临界区】VOIDWINAPILeaveCriticalSection(LPCRITICALSECTIONlpCriticalSection); 临界区在使用时,以CRITICALSECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数占有和释放一个临界区。所用到的CRITICALSECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。 【示例】includeiostreamincludecstdlibincludecstdioincludectimeincludewindows。hintcountervalue0;countervalueintmaxcounter5;countervaluemaxintmincounter0;countervalueminintproducernum0;生产者进入临界区次数intconsumernum0;消费者进入临界区次数CRITICALSECTION临界区DWORDWINAPIproducer(LPVOIDparam){intid(int)while(true){Sleep(rand()1000);srand(countervalue);EnterCriticalSection(criticalsection);intaddcountrand()61;判断是否超过最大值if(countervaluemaxcounter){if(countervaluemaxcounter){}printf(Producerd:producedditems,id,addcount);}else{printf(Producerd:countervalueisfull,cancelproducing。。。,id);}printf(itemsnumisd,countervalue);LeaveCriticalSection(criticalsection);生产者进入临界区,次数增加}}DWORDWINAPIconsumer(LPVOIDparam){intid(int)while(true){sleepforarandomperiodoftimeSleep(rand()1000);EnterCriticalSection(criticalsection);generatesrand(countervalue);intdecreasecountrand()61;判断是否超过最小值if(countervaluemincounter){if(countervaluemincounter){}printf(Consumerd:consumedditems,id,decreasecount);}else{printf(Consumerd:countervalueislessthanmixinum,cancelconsuming。。。,id);}printf(itemsnumisd,countervalue);LeaveCriticalSection(criticalsection);消费者进入临界区,次数增加}return0;}intmain(){srand(countervalue);intthreadproducer5;intthreadconsumer5;intpvalue〔5〕{0};生产者intcvalue〔5〕{0};消费者DWORDthreadpid〔5〕,threadcid〔5〕;HANDLEhthreadp〔5〕,hthreadc〔5〕;生产者和消费者线程InitializeCriticalSection(criticalsection);FILEfreopens(fp,CriticalSectionoutput。txt,w,stdout);createproducerthreadfor(inti0;i){pvalue〔i〕i1;hthreadp〔i〕CreateThread(NULL,0,producer,pvalue〔i〕,0,threadpid〔i〕);}createconsumerthreadfor(inti0;i){cvalue〔i〕i1;hthreadc〔i〕CreateThread(NULL,0,consumer,cvalue〔i〕,0,threadcid〔i〕);}Sleep(1000);for(inti0;i){WaitForSingleObject(hthreadp〔i〕,INFINITE);}for(inti0;i){WaitForSingleObject(hthreadc〔i〕,INFINITE);}DeleteCriticalSection(criticalsection);fclose(stdout);return0;}Mutex 互斥量(Mutex):只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。 【创建互斥量】HANDLEWINAPICreateMutex(LPSECURITYATTRIBUTESlpMutexAttributes,pointertosecurityattributesBOOLbInitialOwner,flagforinitialownershipLPCTSTRlpNamepointertomutexobjectname);参数意义:lpMutextAttributes传递安全相关的配置信息,使用默认安全设置时可以传递NULLbInitialOwner如果为TRUE,则创建出的互斥量对象属于调用该函数的线程,同时进入nonsignaled状态;如果为FALSE,则创建出的互斥量对象不属于任何线程,此时状态为signaledlpName用于命名互斥量对象。传入NULL时创建无名的互斥量对象 【销毁互斥量】BOOLWINAPICloseHandle(HANDLEhObject); 【获取互斥量】获取函数Windows线程创建中介绍的此函数,用于针对单个内核对象验证signaled。DWORDWINAPIWaitForSingleObject(HANDLEhHandle,handletoobjecttowaitforDWORDdwMillisecondstimeoutintervalinmilliseconds); 【释放互斥量】BOOLWINAPIReleaseMutex(HANDLEhMutex需要释放的对象的句柄); 互斥量被某一线程获取时为nonsignaled状态,释放时进入signaled状态。因此,可以利用WaitForSingleObject函数验证互斥量是否已分配。互斥量在WaitForSingleObject函数返回时自动进入nonsignaled状态,因为它是autoreset模式的内核对象。 【示例】includeiostreamincludecstdlibincludecstdioincludectimeincludewindows。hintcountervalue0;countervalueintmaxcounter5;countervaluemaxintmincounter0;countervalueminintproducernum0;生产者进入临界区次数intconsumernum0;消费者进入临界区次数HANDLEMutexNULL;互斥锁DWORDWINAPIproducer(LPVOIDparam){intid(int)while(true){Sleep(rand()1000);srand(countervalue);WaitForSingleObject(Mutex,INFINITE);intaddcountrand()61;判断是否超过最大值if(countervaluemaxcounter){if(countervaluemaxcounter){}printf(Producerd:producedditems,id,addcount);}else{printf(Producerd:countervalueisfull,cancelproducing。。。,id);}printf(itemsnumisd,countervalue);ReleaseMutex(Mutex);生产者进入临界区,次数增加}}DWORDWINAPIconsumer(LPVOIDparam){intid(int)while(true){sleepforarandomperiodoftimeSleep(rand()1000);WaitForSingleObject(Mutex,INFINITE);generatesrand(countervalue);intdecreasecountrand()61;判断是否超过最小值if(countervaluemincounter){if(countervaluemincounter){}printf(Consumerd:consumedditems,id,decreasecount);}else{printf(Consumerd:countervalueislessthanmixinum,cancelconsuming。。。,id);}printf(itemsnumisd,countervalue);ReleaseMutex(Mutex);消费者进入临界区,次数增加}return0;}intmain(){srand(countervalue);intthreadproducer5;intthreadconsumer5;intpvalue〔5〕{0};生产者intcvalue〔5〕{0};消费者DWORDthreadpid〔5〕,threadcid〔5〕;HANDLEhthreadp〔5〕,hthreadc〔5〕;生产者和消费者线程MutexCreateMutex(NULL,FALSE,NULL);FILEfreopens(fp,CriticalSectionoutput。txt,w,stdout);createproducerthreadfor(inti0;i){pvalue〔i〕i1;hthreadp〔i〕CreateThread(NULL,0,producer,pvalue〔i〕,0,threadpid〔i〕);}createconsumerthreadfor(inti0;i){cvalue〔i〕i1;hthreadc〔i〕CreateThread(NULL,0,consumer,cvalue〔i〕,0,threadcid〔i〕);}Sleep(1000);for(inti0;i){WaitForSingleObject(hthreadp〔i〕,INFINITE);}for(inti0;i){WaitForSingleObject(hthreadc〔i〕,INFINITE);}CloseHandle(Mutex);fclose(stdout);return0;}Semaphore 信号量(Semaphore)是维护0到指定最大值之间的同步对象。信号量状态在其计数大于0时是有信号,而其计数是0时是无信号的。信号量对象在控制上可以支持有限数量共享资源的访问。 信号量的特点和用途可用下列几句话定义:如果当前资源的数量大于0,则信号量有效;如果当前资源数量是0,则信号量无效;系统决不允许当前资源的数量为负值;当前资源数量决不能大于最大资源数量。 【创建信号量】HANDLEWINAPICreateSemaphore(LPSECURITYATTRIBUTESlpSemaphoreAttributes,信号量的安全属性LONGlInitialCount,开始时可供使用的资源数LONGlMaximumCount,最大资源数LPCWSTRlpName信号量的名称); 【释放信号量】BOOLWINAPIReleaseSemaphore(HANDLEhSemaphore,要增加的信号量句柄LONGlReleaseCount,信号量的当前资源数增加lReleaseCountLPLONGlpPreviousCount增加前的数值返回); 【打开信号量】HANDLEWINAPIOpenSemaphore(DWORDdwDesiredAccess,accessBOOLbInheritHandle,如果允许子进程继承句柄,则设为TRUELPCWSTRlpName指定要打开的对象的名字); 【销毁信号量】BOOLWINAPICloseHandle(HANDLEhObject); 【示例】includeiostreamincludecstdlibincludecstdioincludectimeincludewindows。hintsemaphorenum1;定义全局变量HANDLEhSemaphoreNULL;定义信号量句柄DWORDWINAPIThreadFunction(LPVOIDparam){intid(int)longresult0;while(semaphorenum100){WaitForSingleObject(hSemaphore,INFINITE);printf(threaddusesemaphorenum:d,id,semaphorenum);ReleaseSemaphore(hSemaphore,1,result);Sleep(1000);}returnNULL;}intmain(){HANDLEhThread〔5〕{NULL};intthread〔5〕{0};DWORDthreadid〔5〕{0};hSemaphoreCreateSemaphore(NULL,1,100,Lsema);for(inti0;i5;i){thread〔i〕i1;hThread〔i〕CreateThread(NULL,0,ThreadFunction,thread〔i〕,0,threadid〔i〕);}Sleep(1000);for(inti0;i5;i){WaitForSingleObject(hThread〔i〕,INFINITE);}CloseHandle(hSemaphore);return0;}Event 事件(Event):是WIN32提供的最灵活的线程间同步方式,事件可以处于激发状态(signaledortrue)或未激发状态(unsignalorfalse)。根据状态变迁方式的不同,事件可分为两类:手动设置:这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。自动恢复:一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。 【创建事件】HANDLEWINAPICreateEvent(LPSECURITYATTRIBUTESlpEventAttributes,BOOLbManualReset,BOOLbInitialState,LPCWSTRlpName);参数说明:lpEventAttributes安全配置相关参数,采用默认安全配置时传入NULLbManualReset传入TRUE时创建manualreset模式的事件对象,传入FALSE时创建autoreset模式的事件对象bInitialState传入TRUE时创建signaled状态,传入FALSE时创建nonsignaled状态的事件对象lpName用于命名事件对象。传递NULL时创建无名的事件对象 当第二个参数传入TRUE时将创建manualreset模式的事件对象,此时即使WaitForSingleObject函数返回也不会回到nonsignaled状态。因此,在这种情况下,需要通过如下2个函数明确更改对象状态。 【打开事件】HANDLEWINAPIOpenEvent(DWORDdwDesiredAccess,BOOLbInheritHandle,LPCSTRlpName); 【复位事件】BOOLWINAPIResetEvent(HANDLEhEvent); 【设置事件】BOOLWINAPISetEvent(HANDLEhEvent); 传递事件对象句柄并希望改为nonsigned状态时,应调用ResetEvent函数。如果希望改为signaled状态,则可以调用SetEvent函数。 【示例】includeiostreamincludecstdlibincludecstdioincludectimeincludewindows。hinteventnum1;定义全局变量HANDLEhEventNULL;定义事件句柄DWORDWINAPIThreadFunction(LPVOIDparam){intid(int)longresult0;while(eventnum100){WaitForSingleObject(hEvent,INFINITE);printf(threaddusesemaphorenum:d,id,eventnum);SetEvent(hEvent);Sleep(1000);}returnNULL;}intmain(){HANDLEhThread〔5〕{NULL};intthread〔5〕{0};DWORDthreadid〔5〕{0};hEventCreateEvent(NULL,FALSE,TRUE,Levent);for(inti0;i5;i){thread〔i〕i1;hThread〔i〕CreateThread(NULL,0,ThreadFunction,thread〔i〕,0,threadid〔i〕);}Sleep(1000);for(inti0;i5;i){WaitForSingleObject(hThread〔i〕,INFINITE);}CloseHandle(hEvent);return0;}