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

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

WaitForMultipleObjects函数及原子操作Interlocked系列函数相关推荐

  1. 秒杀多线程第三篇 原子操作 Interlocked系列函数

    上一篇<多线程第一次亲密接触 CreateThread与_beginthreadex本质区别>中讲到一个多线程报数功能.为了描述方便和代码简洁起见,我们可以只输出最后的报数结果来观察程序是 ...

  2. windows线程同步-原子操作-Interlocked系列函数(用户模式)

    Interlocked系列函数用来保证原子访问. InterlockedExchangeAdd提供保证long类型的原子操作. InterlockedExchangeAdd64提供long long ...

  3. 多线程笔记--原子操作Interlocked系列函数

    前面写了一个多线程报数的功能,为了描述方便和代码简洁起见,只输出最后的报数结果来观察程序运行结果.这非常类似一个网站的客户访问统计,每个用户登录用一个线程模拟,线程运行时将一个表示计数的变量递增.程序 ...

  4. 【Excel函数】-Excel字符串系列函数之Find查找函数

    在Excel函数中,有非常多的Excel的常规函数使用的场景,我们本篇文章就和大家一起讨论下Excel的文本系列函数的使用.我们将在本次文章分享中分享如下函数的使用: Find 函数 Search 函 ...

  5. OpenCV3学习(5.2)——图像修复inpaint函数和图像去噪fastNlMeansDenoising系列函数

    inpaint图像修复 利用inpaint函数进行图像修复.函数原型: CV_EXPORTS_W void inpaint( InputArray src, InputArray inpaintMas ...

  6. 《Windows核心编程》---Interlocked原子访问系列函数

    所谓原子访问,指的是一个线程在访问某个资源的同时能够保证没有其他线程会在同一时刻访问同一资源.Interlocked系列函数提供了这样的操作.所有这些函数会以原子方式来操控一个值. Interlock ...

  7. Interlocked原子访问系列函数

    所谓原子访问,指的是一个线程在访问某个资源的同时能够保证没有其他线程会在同一时刻访问同一资源.Interlocked系列函数提供了这样的操作.所有这些函数会以原子方式来操控一个值. Interlock ...

  8. openssl之EVP系列之11---EVP_Verify系列函数介绍

    openssl之EVP系列之11---EVP_Verify系列函数介绍     ---根据openssl doc/crypto/EVP_VerifyInit.pod翻译和自己的理解写成     (作者 ...

  9. 浅析php curl_multi_*系列函数进行批量http请求

    何起: 一系列 数量很大 数据不热 还希望被蜘蛛大量抓取的页面,在蜘蛛抓取高峰时,响应时间会被拉得很高. 前人做了这样一个事儿:页面分3块,用3个内部接口提供,入口文件用curl_multi_*系列函 ...

最新文章

  1. a_2可以用作python标识符嘛,【单选题】下列选项中,可作为Python标识符的是哪项? A. getpath() B. throw C. my#var D. _ My_price...
  2. python安装mysqldb模块
  3. html中<pre>标签
  4. [自定义区间-Range]书里的例子 - 中文数字类
  5. 浙江大学计算机与机械工程,中国26所“机械工程”大学经调整、合并,浙大、西交、同济升级...
  6. c语言程序设计19,C语言程序设计19.pdf
  7. 正则表达式就这么简单!
  8. Java集合框架源码解读(1)——ArrayList、LinkedList和Vector
  9. ES6深入浅出_汇总贴
  10. kitti点云地图拼接
  11. 解决windows10下总是很快自动黑屏进入睡眠问题
  12. delphi基本语法(摘自博主:沈金强)
  13. 聊一聊我在移动平台混合开发的经验
  14. CVPR ECCV ICCV 计算机视觉顶会论文下载
  15. 利用JS事件让你更加愉快地划水
  16. Android 连接蓝牙耳机后视频通话无声音问题
  17. 经典著作《动手学深度学习》中文版2.0beta版发布!开源下载!
  18. 服务器无法远程连接原因分析
  19. php网页报错500,phpweb 500错误提示 排查修复
  20. 通信学子就业岗位类型简介——GXC

热门文章

  1. python记录(5)- find() 与 rfind()
  2. Matlab常用函数:rand,randi和randn区别
  3. 让远程传输大文件变得更快
  4. Jenkins-安装jenkins2.7.1版本
  5. 这些优化 Drupal 网站速度的超简单办法,你忽略了多少?
  6. Hive过滤脏数据的一些经验
  7. php __FILE__ __DIR__魔术常量的使用【PHP进阶教程】
  8. (转)用Ajax技术让IE Web Control Tree View实现大数据量读取
  9. [收藏]实践参考:parted创建硬盘分区并创建LVM
  10. C++ Builder 实现动态生成窗口、控件,以及处理控件事件(转载)