(转)原子操作 Interlocked系列函数

上一篇《多线程第一次亲密接触 CreateThread与_beginthreadex本质区别》中讲到一个多线程报数功能。为了描述方便和代码简洁起见,我们可以只输出最后的报数结果来观察程序是否运行出错。这也非常类似于统计一个网站每天有多少用户登录,每个用户登录用一个线程模拟,线程运行时会将一个表示计数的变量递增。程序在最后输出计数的值表示有今天多少个用户登录,如果这个值不等于我们启动的线程个数,那显然说明这个程序是有问题的。整个程序代码如下:

  1. #include <stdio.h>
  2. #include <process.h>
  3. #include <windows.h>
  4. volatile long g_nLoginCount; //登录次数
  5. unsigned int __stdcall Fun(void *pPM); //线程函数
  6. const int THREAD_NUM = 10; //启动线程数
  7. unsigned int __stdcall ThreadFun(void *pPM)
  8. {
  9. Sleep(100); //some work should to do
  10. g_nLoginCount++;
  11. Sleep(50);
  12. return 0;
  13. }
  14. int main()
  15. {
  16. g_nLoginCount = 0;
  17. HANDLE  handle[THREAD_NUM];
  18. for (int i = 0; i < THREAD_NUM; i++)
  19. handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
  20. WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  21. printf("有%d个用户登录后记录结果是%d\n", THREAD_NUM, g_nLoginCount);
  22. return 0;
  23. }
 

程序中模拟的是10个用户登录,程序将输出结果:

上一篇的线程报数程序一样,程序输出的结果好象并没什么问题。下面我们增加点用户来试试,现在模拟50个用户登录,为了便于观察结果,在程序中将50个用户登录过程重复20次,代码如下:

  1. #include <stdio.h>
  2. #include <windows.h>
  3. volatile long g_nLoginCount; //登录次数
  4. unsigned int __stdcall Fun(void *pPM); //线程函数
  5. const DWORD THREAD_NUM = 50;//启动线程数
  6. DWORD WINAPI ThreadFun(void *pPM)
  7. {
  8. Sleep(100); //some work should to do
  9. g_nLoginCount++;
  10. Sleep(50);
  11. return 0;
  12. }
  13. int main()
  14. {
  15. printf("     原子操作 Interlocked系列函数的使用\n");
  16. printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
  17. //重复20次以便观察多线程访问同一资源时导致的冲突
  18. int num= 20;
  19. while (num--)
  20. {
  21. g_nLoginCount = 0;
  22. int i;
  23. HANDLE  handle[THREAD_NUM];
  24. for (i = 0; i < THREAD_NUM; i++)
  25. handle[i] = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
  26. WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  27. printf("有%d个用户登录后记录结果是%d\n", THREAD_NUM, g_nLoginCount);
  28. }
  29. return 0;
  30. }

运行结果如下图:

现在结果水落石出,明明有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()函数就可以了。将线程函数代码改成:

  1. DWORD WINAPI ThreadFun(void *pPM)
  2. {
  3. Sleep(100);//some work should to do
  4. //g_nLoginCount++;
  5. InterlockedIncrement((LPLONG)&g_nLoginCount);
  6. Sleep(50);
  7. return 0;
  8. }

再次运行,可以发现结果会是唯一的。

因此,在多线程环境下,我们对变量的自增自减这些简单的语句也要慎重思考,防止多个线程导致的数据访问出错。更多介绍,请访问MSDN上Synchronization Functions这一章节,地址为 http://msdn.microsoft.com/zh-cn/library/aa909196.aspx

看到这里,相信本系列首篇《秒杀多线程第一篇 多线程笔试面试题汇总》中选择题第一题(百度笔试题)应该可以秒杀掉了吧(知其然也知其所以然),正确答案是D。另外给个附加问题,程序中是用50个线程模拟用户登录,有兴趣的同学可以试下用100个线程来模拟一下(上机试试绝对会有意外发现^_^)。

下一篇《秒杀多线程第四篇 一个经典多线程同步问题》将提出一个稍为复杂点但却非常经典的多线程同步互斥问题,这个问题会采用不同的方法来解答,从而让你充分熟练多线程同步互斥的“招式”。更多精彩,欢迎继续参阅。

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7429155

如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。

时间: 2024-11-05 14:57:08

(转)原子操作 Interlocked系列函数的相关文章

原子操作 Interlocked系列函数

上一篇<多线程第一次亲密接触 CreateThread与_beginthreadex本质区别>中讲到一个多线程报数功能.为了描述方便和代码简洁起见,我们可以只输出最后的报数结果来观察程序是否运行出错.这也非常类似于统计一个网站每天有多少用户登录,每个用户登录用一个线程模拟,线程运行时会将一个表示计数的变量递增.程序在最后输出计数的值表示有今天多少个用户登录,如果这个值不等于我们启动的线程个数,那显然说明这个程序是有问题的.整个程序代码如下: [cpp] view plain copy #inc

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

版权声明:本文为博主原创文章,未经博主允许不得转载. 上一篇<多线程第一次亲密接触 CreateThread与_beginthreadex本质区别>中讲到一个多线程报数功能.为了描述方便和代码简洁起见,我们可以只输出最后的报数结果来观察程序是否运行出错.这也非常类似于统计一个网站每天有多少用户登录,每个用户登录用一个线程模拟,线程运行时会将一个表示计数的变量递增.程序在最后输出计数的值表示有今天多少个用户登录,如果这个值不等于我们启动的线程个数,那显然说明这个程序是有问题的.整个程序代码如下:

[OS] 多线程--原子操作 Interlocked系列函数

转自:http://blog.csdn.net/morewindows/article/details/7429155 上一篇<多线程--第一次亲密接触 CreateThread与_beginthreadex本质区别>中讲到一个多线程报数功能.为了描述方便和代码简洁起见,我们可以只输出最后的报数结果来观察程序是否运行出错.这也非常类似于统计一个网站每天有多少用户登录,每个用户登录用一个线程模拟,线程运行时会将一个表示计数的变量递增.程序在最后输出计数的值表示有今天多少个用户登录,如果这个值不等

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

前面写了一个多线程报数的功能,为了描述方便和代码简洁起见,只输出最后的报数结果来观察程序运行结果.这非常类似一个网站的客户访问统计,每个用户登录用一个线程模拟,线程运行时将一个表示计数的变量递增.程序在最后输出这个计数的值表示今天有多少用户登录.如果这个值不等于我们启动的线程个数,那这个程序就是有问题的. #include <stdio.h> #include <process.h> #include <Windows.h> volatile long g_nLogin

多线程面试秒杀系列4---Interlocked系列函数的简要分析

上一篇中我们出现了脏读的问题,但是却没有给出解决办法这一篇中我们这一篇中主要说明一下interlocked系列函数. 下面列出一些常用的Interlocked系列函数: 1.增减操作 LONG__cdeclInterlockedIncrement(LONG volatile* Addend); LONG__cdeclInterlockedDecrement(LONG volatile* Addend); 返回变量执行增减操作之后的值. LONG__cdec InterlockedExchange

PHP 使用 curl_* 系列函数和 curl_multi_* 系列函数进行多接口调用时的性能对比

在页面中调用的服务较多时,使用并行方式,即使用 curl_multi_* 系列函数耗时要小于 curl_* 系列函数. 测试环境 操作系统:Windows 10 x64 Server:Apache 2.4.18 PHP:5.6.19 MySQL:5.7.11 cURL:7.47.1 测试数据库选择 MySQL 官方网站的样本数据库 sakila,下载地址:http://dev.mysql.com/doc/index-other.html 测试页面需要调用 3 个 api: getActorInf

openssl之EVP系列之10---EVP_Sign系列函数介绍

---依据openssl doc/crypto/EVP_SignInit.pod翻译 (作者:DragonKing, Mail: [email protected] ,公布于:http://openssl.126.com 之openssl专业论坛,版本号:openssl-0.9.7) EVP_Sign系列函数使用的基础结构跟信息摘要算法使用的基础结构是一样的.并且,其前面的两个操作步骤初始化和数据操作(信息摘要)也跟信息摘要算法是一样的,唯一不一样的是最后一步操作.本系列函数做了签名的工作,而信

Winsock系列函数 及 Socket通信流程

Socket是一种网络通信机制 Winsock系列函数 1. Socket 创建socket 2. Connect 尝试连接远端Socket 3. Send 在某个Socket 向远端发送数据 4. Recv 接收远端数据 5. Closesocket 关闭连接 6. Listen 在某个Socket上建立监听 7. Accept 接收一条新的连接 (是接收连接而不是接收数据) 8. Bind 给一个Socket分配一个本地协议地址 9. Select 用于检测Socket状态,用于高级通信 1

Linux中backtrace()系列函数的应用实例

一.引言 backtrace()系列函数可用来输出代码出错时的函数调用关系. A backtrace is the series of currently active function calls for the program. #include <execinfo.h> int backtrace(void **buffer, int size); char **backtrace_symbols(void *const *buffer, int size); void backtrac