`

WaitForMultipleObjects函数及原子操作Interlocked系列函数

 
阅读更多

先说一说这一篇用到的多线程等待函数:如下

 

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* AddendLONGValue);

返回运算后的值,注意!加个负数就是减。

 

2.赋值操作

LONG__cdeclInterlockedExchange(LONG volatile* TargetLONGValue);

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

 

  • 大小: 9.8 KB
  • 大小: 11.5 KB
  • 大小: 20.9 KB
  • 大小: 95 KB
  • 大小: 21.6 KB
  • 大小: 18.9 KB
分享到:
评论

相关推荐

    多线程同步演示(采用CreateThread()和WaitForMultipleObjects()函数)

    `CreateThread()`和`WaitForMultipleObjects()`是Windows API中的两个关键函数,用于创建和管理线程以及同步线程间的操作。以下是对这两个函数及其在多线程同步演示中的应用的详细解释。 首先,`CreateThread()`...

    多线程比赛WaitForMultipleObjects()函数的应用.rar

    `WaitForMultipleObjects()`函数是Windows操作系统提供的一种高级同步机制,它允许一个线程等待多个对象的状态变化,从而有效地管理和协调多个线程之间的协作。在这个"多线程比赛WaitForMultipleObjects()函数的应用...

    快速秒杀windows多线程

    7. Interlocked系列函数:由于多线程环境下的数据竞争问题,Windows提供了Interlocked系列函数来进行原子操作。原子操作保证了在多线程环境下,对变量的操作是不可分割的,确保了操作的原子性。在文章中提到的g_...

    操作系统实验-线程的同步.doc

    Wait 函数包括 WaitForSingleObject 和 WaitForMultipleObjects 两个 API 函数。 WaitForSingleObject 函数用于等待一个对象,等待的对象可以是变化通知、控制台输入、事件、作业、互斥信号量、进程、计数信号量、...

    Windows API函数实用手册大全

    Windows API是Windows操作系统提供的一系列函数、常量、结构体和枚举类型,它为应用程序提供与操作系统交互的能力,涵盖了窗口管理、输入输出、网络通信、文件操作等多个方面。 1. **窗口管理**:Windows API提供了...

    Windows C++ API函数大全

    Windows API是Microsoft Windows操作系统提供的一系列函数库,为开发者提供了控制操作系统、访问硬件资源、创建图形用户界面等核心功能。 在Windows环境下,C++程序员通常会使用Windows API来实现系统级别的功能,...

    Windows api函数大全

    Windows API(Application Programming Interface)是微软为开发者提供的一个庞大的函数库,它包含了各种与操作系统交互的函数,使得程序员能够创建能在Windows操作系统上运行的应用程序。API函数覆盖了从图形用户...

    WINDOWS api 函数大全

    Windows API 函数大全文档可能还包含了每个函数的详细参数解释、返回值、错误处理及使用示例,这对于开发者理解和应用这些函数非常有帮助。通过深入学习和实践,开发者可以充分利用这些API来构建高效、稳定的Windows...

    《windowsAPI函数》中文手册

    《Windows API 函数》中文手册是一本针对程序员和软件开发者的重要参考资料,它详细阐述了Windows操作系统下编程所使用的API(应用程序接口)函数。Windows API是Windows操作系统提供给开发者的一个庞大而全面的接口...

    Windows提供的API函数

    在Windows操作系统中,API(应用程序接口)是一组预先定义的函数,允许程序员通过调用这些函数来实现特定的功能。在本主题中,我们将探讨如何利用Windows API来创建和终止进程,以此来理解操作系统中的进程概念,并...

    基于API函数的串口通信编程

    `CreateThread`和`CloseHandle`函数用于创建和关闭线程,`WaitForSingleObject`和`WaitForMultipleObjects`用于线程间的同步,确保在适当的时候进行串口读写。 3. 实现消息机制的API函数: 为了处理串口通信事件,...

    Window API函数名

    在Windows操作系统中,API(应用程序接口)是一组预先定义的函数,允许程序员通过调用这些函数来实现与操作系统交互。Windows API提供了丰富的功能,包括窗口管理、设备驱动、图形绘制、网络通信、文件操作等。以下...

    几百个常用的 API 函数的简介

    在VC++环境中,API函数是Windows操作系统提供的核心功能接口,它们允许程序员访问和控制操作系统的服务,如文件操作、窗口管理、网络通信等。 在"几百个常用的API函数的简介"中,我们可以期待涵盖以下几类主要的...

    cbt 钩子,系统托盘与等待函数.rar

    例如,WaitForSingleObject和WaitForMultipleObjects函数,这两个函数可以让线程等待一个或多个对象(如事件、互斥量或信号量)的状态改变,从而实现同步操作。在Windows应用程序中,正确使用等待函数可以有效地避免...

    WINDOWS下API 函数

    在Windows操作系统中,API(应用程序接口)是一组预先定义的函数、常量、结构体和其他编程元素,允许程序员实现各种操作系统级别的功能。Windows API函数是开发者构建Windows应用程序的基础,涵盖了从用户界面到系统...

    MicrosoftWin32程序参考大全 系列

    I和J开头的函数在Win32 API中相对较少,但`InterlockedCompareExchange`等原子操作函数对于多线程编程至关重要,保证了数据在并发环境下的安全。 K到L的函数中,`KillTimer`用于取消已设置的定时器,`LoadCursor`和...

    windows程序设计,学习基本的常见windows的函数

    在Windows程序设计中,我们通常会接触到一系列与操作系统交互的核心函数,这些函数是构建桌面应用程序的基础。本主题将深入探讨Windows API(应用程序接口)中的基本函数,帮助开发者理解和掌握如何在Windows平台上...

    WindowsAPI 中文详解

    `ExitThread`结束线程,而`Interlocked`系列函数用于线程安全的变量操作。 ### 7. 注册表操作 通过`RegCreateKeyEx`、`RegOpenKeyEx`、`RegSetValueEx`和`RegQueryValueEx`等函数,开发者可以读写Windows注册表,...

    Windows C函数一览.rar

    Windows API(应用程序接口)提供了大量的函数,这些函数允许C语言程序与操作系统进行交互。以下是一些重要的Windows C函数类别及其功能: 1. **进程和线程管理**:如CreateProcess()用于创建新进程,CreateThread...

Global site tag (gtag.js) - Google Analytics