0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于多线程环境下值的递增操作--原子操作

C语言专家集中营 2018-01-10 11:16 次阅读

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

[cpp]view plaincopy

#include

#include

#include

volatilelongg_nLoginCount;//登录次数

unsignedint__stdcallFun(void*pPM);//线程函数

constintTHREAD_NUM=10;//启动线程数

unsignedint__stdcallThreadFun(void*pPM)

{

Sleep(100);//someworkshouldtodo

g_nLoginCount++;

Sleep(50);

return0;

}

intmain()

{

g_nLoginCount=0;

HANDLEhandle[THREAD_NUM];

for(inti=0;i< THREAD_NUM; i++)  

handle[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,NULL);

WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE);

printf("有%d个用户登录后记录结果是%d\n",THREAD_NUM,g_nLoginCount);

return0;

}

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

基于多线程环境下值的递增操作--原子操作

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

[cpp]view plaincopy

#include

#include

volatilelongg_nLoginCount;//登录次数

unsignedint__stdcallFun(void*pPM);//线程函数

constDWORDTHREAD_NUM=50;//启动线程数

DWORDWINAPIThreadFun(void*pPM)

{

Sleep(100);//someworkshouldtodo

g_nLoginCount++;

Sleep(50);

return0;

}

intmain()

{

printf("原子操作Interlocked系列函数的使用\n");

printf("--byMoreWindows(http://blog.csdn.net/MoreWindows)--\n\n");

//重复20次以便观察多线程访问同一资源时导致的冲突

intnum=20;

while(num--)

{

g_nLoginCount=0;

inti;

HANDLEhandle[THREAD_NUM];

for(i=0;i< THREAD_NUM; i++)  

handle[i]=CreateThread(NULL,0,ThreadFun,NULL,0,NULL);

WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE);

printf("有%d个用户登录后记录结果是%d\n",THREAD_NUM,g_nLoginCount);

}

return0;

}

运行结果如下图:

基于多线程环境下值的递增操作--原子操作

现在结果水落石出,明明有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(LONGvolatile*Addend);

LONG__cdeclInterlockedDecrement(LONGvolatile*Addend);

返回变量执行增减操作之后的值。

LONG__cdecInterlockedExchangeAdd(LONGvolatile*Addend,LONGValue);

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

2.赋值操作

LONG__cdeclInterlockedExchange(LONGvolatile*Target,LONGValue);

Value就是新值,函数会返回原先的值。

在本例中只要使用InterlockedIncrement()函数就可以了。将线程函数代码改成:

[cpp]view plaincopy

DWORDWINAPIThreadFun(void*pPM)

{

Sleep(100);//someworkshouldtodo

//g_nLoginCount++;

InterlockedIncrement((LPLONG)&g_nLoginCount);

Sleep(50);

return0;

}

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

基于多线程环境下值的递增操作--原子操作

因此,在多线程环境下,我们对变量的自增自减这些简单的语句也要慎重思考,防止多个线程导致的数据访问出错。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 多线程
    +关注

    关注

    0

    文章

    277

    浏览量

    19921
  • C++
    C++
    +关注

    关注

    22

    文章

    2104

    浏览量

    73487
  • 递增
    +关注

    关注

    0

    文章

    3

    浏览量

    6677

原文标题:原子操作 Interlocked系列函数

文章出处:【微信号:C_Expert,微信公众号:C语言专家集中营】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    一文详解RTOS开发中的原子操作

    裸机开发与RTOS开发一个非常重要的区别在于多线程之间的消息传递和数据共享问题,然而在这中间变量的原子操作是一个非常重要的话题,不同的处理器架构和编译选项都可能生成不同的指令,从而影响到变量的
    发表于 11-17 09:43 1687次阅读

    Java多线程的用法

    本文将介绍一Java多线程的用法。 基础介绍 什么是多线程 指的是在一个进程中同时运行多个线程,每个线程都可以独立执行不同的任务或
    的头像 发表于 09-30 17:07 924次阅读

    LabView的多线程语言

    LabView的多线程语言以前只会照猫画虎的写一些简单的程序,一些基本原理不是很清晰。从网上找了一些资料,这里总结一。1。一般情况,运行一个 VI,至少有两个线程:一个界面
    发表于 06-08 10:13

    原子操作指令的作用

    一些常用操作原子指令,这些原子指令的作用就是避免多线程同时对一个共享数据进行读写操作。没有原子
    发表于 09-18 21:39

    Java操作系统支持多线程

    Windows等操作系统均支持多线程进程的并发处理机制。操作系统支持多线程,使多个程序能够并发执行,以改善资源使用率和提高系统效率;操作系统
    发表于 08-05 06:06

    基于51单片机的多线程操作系统 精选资料分享

    我知道,在51单片机上运行一个操作系统,大多数情况并不实用。但51单片机广为人知。所以我认为,用它来逐步的实现一个多线程操作系统,使得读者以更多的精力思考
    发表于 07-20 07:55

    如何使用多线程和异步操作等并发设计方法来最大化程序的性能

    (超线程、双核)的普及,多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论。本文主要是想探讨一如何使用并发来最大化程序的性能。  多线程
    发表于 08-23 16:31

    在MCU开发中使用多线程操作一写一读是否需要保护?

    ,那么多线程访问是安全的,那么对于一写一读,在某些情况需要保护,某些情况其实可以不需要保护。当操作数据是 1字节 uint8_t 类型数据,可以不做保护,对于uint8_t类型的数
    发表于 02-01 15:42

    QNX环境多线程编程

    介绍了QNX 实时操作系统和多线程编程技术,包括线程间同步的方法、多线程程序的分析步骤、线程基本程序结构以及实用编译方法。QNX 是由加拿大
    发表于 08-12 17:37 30次下载

    MFC多线程编程

    计算机上的上位机制作工具语言之MFC多线程编程
    发表于 09-01 14:55 0次下载

    linux多线程编程技术

    1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的 Unix也支持线程的概念,但是在一个进
    发表于 10-24 16:01 5次下载

    Linux多线程编程

    线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务
    发表于 04-02 14:43 596次阅读

    多线程的情况如何对一个进行 a++ 操作

    多线程的情况,对一个进行 a++ 操作,会出现什么问题? a++ 的问题 先写个 demo 的例子。把 a++ 放入多线程中运行一
    的头像 发表于 10-13 11:17 657次阅读
    在<b class='flag-5'>多线程</b>的情况<b class='flag-5'>下</b>如何对一个<b class='flag-5'>值</b>进行 a++ <b class='flag-5'>操作</b>

    多线程如何保证数据的同步

    。本文将详细介绍多线程数据同步的概念、问题、以及常见的解决方案。 一、多线程数据同步概念 在多线程编程中,数据同步指的是通过某种机制来确保多个线程对共享数据的
    的头像 发表于 11-17 14:22 1155次阅读

    redis使用多线程处理操作命令

    讨 Redis 多线程处理操作命令的实现和优势,帮助读者深入了解这一方面的知识。 首先,我们来了解一 Redis 的基本概念和工作原理。Redis 是一个支持键值对存储的数据库系统,它将数据存储在内存中,从而实现了高速读写
    的头像 发表于 12-05 10:25 545次阅读