先说一说这一篇用到的多线程等待函数:如下
WaitForMultipleObjects( DWORD nCount, CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds );
nCount 表示我们希望函数检查的内核对象的数量。这个值必须在1~ MAXIMUM_WAIT_OBJECTS(64)之间。
lpHandles 是一个指针,指向一个内核对象句柄的数组。
我们可以通过两种方式来使用WaitForMultipleObjects,一种是让线程进入等待状态直到指定内核对象中的一个被触发位置,另一种是让线程进入等待状态直到指定内核对象中的全部被触发为止。
bWaitAll 如果给这个参数传递TRUE,那么在所有内核对象被触发之前,函数将不会允许调用线程继续执行。
dwMilliseconds的用法与waiforsingleobject中完全相同。如果在等待的时候,超出了指定的时间长度,那么即使内核对象还没有被触发,函数也会返回。 我们通常传递INFINITE给这个参数,但为了避免可能出现的死锁,在编写代码的时候应该小心。
WaitForMultipleObjects函数的返回值告诉调用方函数为什么它得以继续运行。可能的返回值包括WAIT_FAILED(失败)和WAIT_TIMEOUT(超时),他们都不言自明。如果给bWaitAll传的是TRUE而且所有对象都被触发了,那么返回值是WAIT_OBJECT_0。
如果给bWaitAll传的是FALSE,那么只要任何一个对象被触发,函数就会立即返回。这时的返回值是WAIT_OBJECT_0和(WAIT_OBJECT_0+nCount -1)之间的任何一个值。即指明了被触发的是哪一个对象。
实例一:
HANDLE h[2]; h[0] = hThread1; h[1] = hThread2; DWORD dw = ::WaitForMultipleObjects(2, h, FALSE, 5000); switch(dw) { case WAIT_FAILED: // 调用WaitForMultipleObjects函数失败(句柄无效?) break; case WAIT_TIMEOUT: // 在5秒内没有一个内核对象受信 break; case WAIT_OBJECT_0 + 0: // 句柄h[0]对应的内核对象受信 break; case WAIT_OBJECT_0 + 1: // 句柄h[1]对应的内核对象受信 break; }
参数bWaitAll为FALSE的时候,WaitForMultipleObjects函数从索引0开始扫描整个句柄数组,第一个受信的内核对象将终止函数的等待,使函数返回。
实例二:
for(int i=0;i<6;i++) { for(int j=0;j<10;j++) { theport[j].rmt_host=rmt_host; theport[j].p=port[i*10+j]; theport[j].n=j; Thread[j]=AfxBeginThread(pScan,(LPVOID)&theport[j]); hThread[j]=Thread[j]->m_hThread; Sleep(1); } WaitForMultipleObjects(10,hThread,TRUE,120000); }
注:线程退出后,即线程对象计数值变为0后,线程才会变为受信状态。
在多线程状态下时,如果传递的是值的拷贝,是不会有问题的,如下:
#include <windows.h> #include <process.h> #include <iostream> using namespace std; const int THREADNUM = 10; unsigned int __stdcall threadFunc(PVOID pM) { Sleep(100); cout << ((int )pM) << endl; return 0; } int main() { HANDLE handle[THREADNUM]; for(int i=0; i< THREADNUM; i++) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFunc, (PVOID) i, 0, NULL); } WaitForMultipleObjects(THREADNUM, handle, TRUE ,INFINITE); getchar(); return 0; }
输出结果为:
如果传递的是地址,则在计数方面会有不安全的问题出现。
#include <windows.h> #include <process.h> #include <iostream> using namespace std; const int THREADNUM = 10; unsigned int __stdcall threadFunc(PVOID pM) { Sleep(100); cout << *((int* )pM) << endl; return 0; } int main() { HANDLE handle[THREADNUM]; for(int i=0; i< THREADNUM; i++) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL); } WaitForMultipleObjects(THREADNUM, handle, TRUE ,INFINITE); getchar(); return 0; }
输出结果为:
由于输出 换行不在临界区, 所以线程并发时,会有同一行的输出出现。
二、 如下代码进行线程个数的统计
#include <windows.h> #include <process.h> #include <iostream> using namespace std; const int THREADNUM = 50; int number = 0; unsigned int __stdcall threadFunc(PVOID pM) { Sleep(100); number++; Sleep(50); return 0; } int main() { int num = 20; HANDLE handle[THREADNUM]; while(num--) { number = 0; for(int i=0; i< THREADNUM; i++) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFunc, NULL, 0, NULL); } WaitForMultipleObjects(THREADNUM, handle, TRUE ,INFINITE); cout << "计数个数为" << number << endl; } getchar(); return 0; }
输出结果为:
现在结果水落石出,明明有50个线程执行了g_nLoginCount++;操作,但结果输出是不确定的,有可能为50,但也有可能小于50。
要解决这个问题,我们就分析下g_nLoginCount++;操作。在VC6.0编译器对g_nLoginCount++;这一语句打个断点,再按F5进入调试状态,然后按下Debug工具栏的Disassembly按钮,这样就出现了汇编代码窗口。可以发现在C/C++语言中一条简单的自增语句其实是由三条汇编代码组成的,如下图所示。
这三条汇编的意思分别为:
第一条汇编将g_nLoginCount的值从内存中读取到寄存器eax中。
第二条汇编将寄存器eax中的值与1相加,计算结果仍存入寄存器eax中。
第三条汇编将寄存器eax中的值写回内存中。
这样由于线程执行的并发性,很可能线程A执行到第二句时,线程B开始执行,线程B将原来的值又写入寄存器eax中,这样线程A所主要计算的值就被线程B修改了。这样执行下来,结果是不可预知的——可能会出现50,可能小于50。
因此在多线程环境中对一个变量进行读写时,我们需要有一种方法能够保证对一个值的递增操作是原子操作——即不可打断性,一个线程在执行原子操作时,其它线程必须等待它完成之后才能开始执行该原子操作。这种涉及到硬件的操作会不会很复杂了,幸运的是,Windows系统为我们提供了一些以Interlocked开头的函数来完成这一任务(下文将这些函数称为Interlocked系列函数)。
下面列出一些常用的Interlocked系列函数:
1.增减操作
LONG__cdeclInterlockedIncrement(LONG volatile* Addend);
LONG__cdeclInterlockedDecrement(LONG volatile* Addend);
返回变量执行增减操作之后的值。
LONG__cdec InterlockedExchangeAdd(LONG volatile* Addend, LONGValue);
返回运算后的值,注意!加个负数就是减。
2.赋值操作
LONG__cdeclInterlockedExchange(LONG volatile* Target, LONGValue);
Value就是新值,函数会返回原先的值。
在本例中只要使用InterlockedIncrement()函数就可以了。将线程函数代码改成:
#include <windows.h> #include <process.h> #include <iostream> using namespace std; const int THREADNUM = 50; volatile long number = 0; unsigned int __stdcall threadFunc(PVOID pM) { Sleep(100); InterlockedIncrement(&number); Sleep(50); return 0; } int main() { int num = 20; HANDLE handle[THREADNUM]; while(num--) { number = 0; for(int i=0; i< THREADNUM; i++) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFunc, NULL, 0, NULL); } WaitForMultipleObjects(THREADNUM, handle, TRUE ,INFINITE); cout << "计数个数为" << number << endl; } getchar(); return 0; }
输出结果为:
因此,在多线程环境下,我们对变量的自增自减这些简单的语句也要慎重思考,防止多个线程导致的数据访问出错。
但是当线程数达到70~100时,会有不稳定的情况出现,
当为70时,运行截图如下:
具体原因在 下一篇文章中解释。
参见http://xiabin1235910-qq-com.iteye.com/admin/blogs/1968781
相关推荐
采用多线程技术,使用CreateThread WaitForMultipleObjects函数等,实现了5个人赛跑比赛的模拟,并统计比赛结果。是一个比较好的多线程的演示。
多线程比赛WaitForMultipleObjects()函数的应用.rar
该程序利用WaitForMultipleObjects函数实现了线程的安全退出。
假定某辆公共汽车上有两名售票员与一名司机,汽车当前正在始发站停车上客,试设必要的信号灯及赋初值,写出他们的同步过程 分析: 司机停车,通知售票员开门,售票员关门,通知司机开车 使用到的函数和信号量 ...
易语言API定义源码,API定义,DefWindowProc,CreateRemoteThread,MsgWaitForMultipleObjects,RectVisible,mciSendStringA,Sleep,SHFormatDrive,ChooseColorA,LocalSize_色盒,LocalSize_EXCEPTION_POINTERS,...
WaitForMultipleObjects()主进程等待线程的结束等函数进行设计。 2.在Windows中,常见的同步对象有:信号量(Semaphore)、互斥量(Mutex) 。 使用这些对象都分为三个步骤,一是创建或者初始化; 接着请求该同步对象,...
WaitForMultipleObjects()主进程等待线程的结束等函数进行设计。 2.在Windows中,常见的同步对象有:信号量(Semaphore)、互斥量(Mutex) 。 使用这些对象都分为三个步骤,一是创建或者初始化; 接着请求该同步对象,...
实现不同线程独立完成自己的任务,每个线程的任务都是向文件写入指定的字符
使用到的函数和信号量 HANDLE mutex; HANDLE empty; HANDLE full; 创建信号量 HANDLE CreateSemaphore( __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,// lpSemaphoreAttributes是信号量的安全属性 可...
通过互斥量、事件来解决生产者和消费者的问题,主要涉及的内容有多线程的创建,事件的创建,互斥量的创建,线程的同步。主要的函数有:CreateThread,CreateEvent,CreateMutex,WaitForMultipleObjects等。
本次大作业主要是对蒙特·卡罗方法进行并行处理,通过OpenMP、MPI、.NET、Java、Win32API等一系列并行技术和并行机制对该算法进行并行处理,从而也进一步熟悉了蒙特·卡罗方法的串行算法和并行算法,实现了用蒙特·...
本次大作业主要是对蒙特·卡罗方法进行并行处理,通过OpenMP、MPI、.NET、Java、Win32API等一系列并行技术和并行机制对该算法进行并行处理,从而也进一步熟悉了蒙特·卡罗方法的串行算法和并行算法,实现了用蒙特·...
改成与CPU个数有关的话,又和WaitForMultipleObjects冲突,不知道如何处理这个数组 5.如何接收新用户连接? 接收了连接后就要投递更足够的AcceptEx操作,当用完一个就再增加投入,这样可以无限制的接收用户。 实现...
WaitForSingleObject与WaitForMultipleObjects 多线程测试。
使用到的函数和信号量 HANDLE mutex; HANDLE empty; HANDLE full; 创建信号量 HANDLE CreateSemaphore( __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,// lpSemaphoreAttributes是信号量的安全属性 可...
先上一些多线程需要使用的函数定义: 代码如下:DWORD WINAPI ProcessClientRequests(LPVOID lpParam) //新线程将会执行的函数定义{ return 0;...WaitForMultipleObjects(MAXCLIENTS, threads, TRUE, INFINITE); //
该库使用的函数WaitForMultipleObjects最多只能等待MAXIMUM_WAIT_OBJECTS句柄*,并且不参与线程池/ APC,就其建模协作式多任务处理而言,后者可能是一个优点-没有控制机制来控制对共享的访问例如,需要资源。...
易语言注册表监视源码,CreateEvent,RegOpenKeyEx,RegCloseKey,CloseHandle,RegNotifyChangeKeyValue,WaitForSingleObject,WaitForMultipleObjects
易语言金盾护航源码,金盾护航,消息框,系统修复,系统设置,系统优化,CreateEvent,RegOpenKeyEx,RegCloseKey,CloseHandle,RegNotifyChangeKeyValue,WaitForSingleObject,WaitForMultipleObjects,MessageBox