版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖。如要转贴,必须注明原文网址

  http://www.cnblogs.com/Colin-Cai/p/7668982.html 

  作者:窗户

  QQ:6679072

  E-mail:6679072@qq.com

  说到原子,类似于以下的代码可能人人都可以看出猫腻。

/* http://www.cnblogs.com/Colin-Cai */
#include <stdio.h>
#include <pthread.h> int cnt = ;
void* mythread(void* arg)
{
int i;
for(i=;i<;i++)
cnt++;
return NULL;
} int main()
{
pthread_t id, id2; pthread_create(&id, NULL, mythread, NULL);
pthread_create(&id2, NULL, mythread, NULL);
pthread_join(id, NULL);
pthread_join(id2, NULL);
printf("cnt = %d\n", cnt); return ;
}

  我想大多数人都知道其结果未必会得到1000000000。

  测试一下吧。

linux-p94b:/tmp/testhere # gcc test1.c -lpthread
linux-p94b:/tmp/testhere # for((i=0;i<10;i++));do ./a.out ; done
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 958925625
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000

  可是真的知道猫腻了吗?如果我编译的时候优化一下呢?

linux-p94b:/tmp/testhere # gcc -O2 test1.c -lpthread
linux-p94b:/tmp/testhere # for((i=0;i<10;i++));do ./a.out ; done
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000

  运行速度一下子变的飞快,而且似乎都得到了10亿。

  这里,mythread里cnt自加5亿次被优化成了 cnt += 500000000

  那么当然快啊,可是似乎这与我们当初想测试原子有那么一些差异,一样的代码,不一样的编译,却带来了不同的结果。

  其实原因在于,我们这里代码写的不好,才没有表达好我们当初的意思,我们是希望cnt真的自加5亿次。那么怎么办呢?其实很好办,在cnt的定义前面加个volatile,那么这里对于cnt的自加则不会优化。很多时候,为什么我们优化前和优化后的结果不一样,常常是因为写代码的人不明白程序的优化规则。在上个公司的时候,我很想临走的时候再给大家做一个培训,说说C语言的优化,同时说说我们平时写的无意依赖于编译的所谓垃圾代码,但是直到离开,我还是没有做此培训。

  我们加了volatile试一下,

linux-p94b:/tmp/testhere # gcc -O2 test1.c -lpthread
linux-p94b:/tmp/testhere # for((i=0;i<10;i++));do ./a.out ; done
cnt = 635981117
cnt = 675792826
cnt = 522700646
cnt = 593410055
cnt = 544306380
cnt = 630888304
cnt = 580539893
cnt = 629360072
cnt = 555570127

  我们在cnt定义前加个volatile,效果果然就更明显了,因为真的是自加5亿次,导致问题的机会变多了。那么之前没加volatile并优化编译,会不会也有不得到10亿的可能呢?

  我们首先要明白的是,这里的cnt++不是原子操作,中间有随时调度的可能。

  5亿次太多,我们就拿只自加1次为例即可说明,两个线程都只自加1次,本来期待结果为2.

  cnt++在一般的处理器中至少有三条指令,我们用伪汇编来写。  

  cnt -> reg  //把cnt从内存加载到寄存器reg

  reg+1 -> reg //寄存器reg自加1

  reg -> cnt     //把reg的内容写入内存

  

  那么,

(线程1)cnt -> reg

  (线程1)reg+1 -> reg

  (线程1)reg -> cnt

(线程2)cnt -> reg

  (线程2)reg+1 -> reg

  (线程2)reg -> cnt

  理想中,我们认为处理器的执行是以上这样,结果cnt里的值是2。

  但假设过程中发生了调度,指令执行的顺序并非像以上这样,假如变成了以下这样

(线程1)cnt -> reg

  (线程1)reg+1 -> reg  

(线程2)cnt -> reg

  (线程2)reg+1 -> reg

  (线程2)reg -> cnt

  (线程1)reg -> cnt

  我们再来算算,

  cnt = 0,  reg任意

(线程1)cnt -> reg

  cnt = 0, reg =  0

  (线程1)reg+1 -> reg

  cnt = 0, reg = 1

  此处调度,reg = 1会被保存,并在重新调度回来之后有效,而cnt不会管

  调度之后

  cnt = 0, reg任意 

(线程2)cnt -> reg

  cnt = 0, reg = 0

  (线程2)reg+1 -> reg

  cnt = 0, reg = 1

  (线程2)reg -> cnt

  cnt = 1, reg = 1

  此处又发生调度,reg会恢复之前保存的1,而cnt不会有任何变化

  所以在执行下一条指令前,

  cnt = 1, reg = 1

  (线程1)reg -> cnt

  cnt = 1, reg = 1

  

  我们可以看到,结果成了1,而不是2,这就是非原子操作导致的结果,其实之前优化成cnt += 500000000本身也依然有此问题,只是难以观察的到。

  虽然x++不是原子,但是我们可以使用锁的方式,来人为的制造“原子”,比如这里用互斥。

  

#include <stdio.h>
#include <pthread.h> volatile int cnt = ;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* mythread(void* arg)
{
int i;
for(i=;i<;i++) {
pthread_mutex_lock(&mutex);
cnt++;
pthread_mutex_unlock(&mutex);
}
return NULL;
} int main()
{
pthread_t id, id2; pthread_create(&id, NULL, mythread, NULL);
pthread_create(&id2, NULL, mythread, NULL);
pthread_join(id, NULL);
pthread_join(id2, NULL);
printf("cnt = %d\n", cnt); return ;
}

  测试一下

linux-p94b:/tmp/testhere # gcc -O2 test1.c -lpthread
linux-p94b:/tmp/testhere # for((i=0;i<10;i++));do ./a.out ; done
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000
cnt = 1000000000

  

C语言/原子/编译,你真的明白了吗?的更多相关文章

  1. 我学C的那些年[ch01]:浅淡C语言的编译过程

    前几天大致学习了C语言的编译过程,那么今天就和大家分享一下 首先,编译C语言,需要一个文本编辑器(windows自带的也行),和一个MinGW编译器(需要配置环境),就可以将.c文件编译成.exe文件 ...

  2. python变量、对象和引用你真的明白了吗

    python变量.对象和引用你真的明白了吗 变量.对象和引用 Python不像C++,Java等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值.对Python语言来讲,对象的类型和内存都是 ...

  3. C语言模块化编译介绍

    C语言模块化编译介绍 模块化编程的概念 所谓模块化变成(多文件开发),就是多文件(.c文件)编程,一个.c文件和一个.h文件可以被称为一个模块. 头文件开发的注意事项: 1)头文件中可以和C程序一样引 ...

  4. C语言的编译过程、安装gcc编译器以及设置环境变量

    以我对C语言编译过程的了解,我用了一点时间画了一个图,提供给大家参考一下,希望有些能对您的问题提上帮助. 前几天刚初步学习了C语言的编译过程,感触挺深的.在C语言中头文件其实起了一个很大的作用. 1. ...

  5. Koala – 开源的前端预处理器语言图形编译工具

    koala 是一个前端预处理器语言图形编译工具,支持 Less.Sass.Compass.CoffeeScript,帮助 Web 开发者更高效地使用它们进行开发.跨平台运行,完美兼容 Windows. ...

  6. 李洪强漫谈iOS开发[C语言-004]-开发概述程序设计语言程序编译过程

    汇编语言 指令用特定的名字来标记,这就是汇编语言 人比较容易看懂汇编语言 汇编直接和程序一一对应的 有汇编器把程序翻译成机器码 把高级语言编译成计算机识别的语言 程序编译过程 命令行 UNIX 系统中 ...

  7. C语言的编译过程和GCC编译参数

    C语言的编译一般有三个步骤: 预编译: gcc -E -o a.e a.c 预编译a.c文件,生成的目标文件名为a.e 预编译就是将include包含的头文件内容替换到C文件中,同时删除代码中没用的注 ...

  8. c语言的编译过程和GCC 编译参数

    原文: http://www.cnblogs.com/zhangShanGui/p/4912135.html C语言的编译过程和GCC编译参数 C语言的编译一般有三个步骤: 预编译: gcc -E - ...

  9. .NET语言的编译过程:中间语言(IL)和即时编译器(JIT)

    .NET语言的编译分为两个阶段.首先高级语言被编译成一种称作IL的中间语言,与高级语言相比,IL更像是机器语言,然而,IL却包含一些抽象概念(比如:类.异常),这也是这种语言被称为中间语言的原因.IL ...

随机推荐

  1. Git简易参考手册

    如果用过mercury(HG),那么理解Git的运作方式就轻松多了.两者是相同的分布式版本管理工具,只是某些功能有着细微的差别 - Git的管理粒度更加细腻,因此操作上也比HG复杂一点.例如,修改文件 ...

  2. 【深入Java虚拟机】之七:Javac编译与JIT编译

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/18009455 编译过程 不论是物理机还是虚拟机,大部分的程序代码从开始编译到最终转化成物理 ...

  3. Beta版本测试报告以及Beta版本发布说明

    Beta版本测试报告 请根据团队项目中软件的需求文档.功能说明.系统设计和Beta阶段的计划安排,写出软件的测试过程和测试结果,并回答下述问题. 在测试过程中总共发现了多少bug?每个类别的bug分别 ...

  4. 201521123122 《java程序设计》第八周实验总结

    201521123122 <java程序设计>第八周实验总结 1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 2. 书面作业 List中指定元素的删除(题目4- ...

  5. 201521123112《Java程序设计》第10周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 1.finally 题目4-2 1.1 截图你的提交结果(出 ...

  6. 201521123029《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 答:1.数据库定义:为了实现一定目的按某种规则组织起来的"数据"的"集合" ...

  7. MySQL数据库引擎介绍、区别

    数据库引擎介绍 MySQL数据库引擎取决于MySQL在安装的时候是如何被编译的.要添加一个新的引擎,就必须重新编译MYSQL.在缺省情况下,MYSQL支持三个引擎:ISAM.MYISAM和HEAP.另 ...

  8. JDBC操作数据库之修改数据

    使用JDBC修改数据库中的数据,起操作方法是和添加数据差不多的,只不过在修改数据的时候还要用到UPDATE语句来实现的,例如:把图书信息id为1的图书数量改为100,其sql语句是:update bo ...

  9. 建一座安全的“天空城” ——揭秘腾讯WeTest如何与祖龙共同挖掘手游安全漏洞

    作者:腾讯WeTest手游安全测试团队商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. WeTest导读 <九州天空城3D>上线至今,长期稳定在APP Store畅销排行的前 ...

  10. Spring MVC 的文件下载

    在看Spring MVC文件下载之前请先看Spring MVC文件上传 地址:http://www.cnblogs.com/dj-blog/p/7535101.html 文件下载比较简单,在超链接中指 ...