版权申明:本文为博主窗户(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. 前端js优化方案(连续更新)

    最近在读<高性能javascript>,在这里记录一下读后的一些感受,顺便加上自己的一些理解,如果有兴趣的话可以关注的我的博客http://www.bloggeng.com/,我会不定期发 ...

  2. CentOS6.5下LNMP环境的搭建

    #写的不好,大牛勿喷 #其实我很努力 OS:CentOS6.5 1.关闭SELinux,关闭防火墙 原因:1.SELinux确实可以提高服务器的安全性,但是对于服务器的性能存在一定的影响,同时它的复杂 ...

  3. 【Beta阶段】第三次scrum meeting

    Coding/OSChina 地址 1. 会议内容 学号 主要负责的方向 昨日任务 昨日任务完成进度 接下去要做 99 PM 打包上传团队代码 100% 查找适合的素材模块,和105一起把手势功能连接 ...

  4. JFrame常用属性设置模板

    最近在学习Swing,在各种demo中都需要构建JFrame,于是我决定把构建JFrame的代码贴上来,以后就直接复制粘贴了. public static void main(String[] arg ...

  5. 201521123015 《Java程序设计》第七周学习总结

    1. 本周学习总结 2. 书面作业 1. ArrayList代码分析 1.1 解释ArrayList的contains源代码 源代码如下: public boolean contains(Object ...

  6. 201521123028 《Java程序设计》第2周学习总结

    1. 本周学习总结 1.学习了String及部分函数用法,例如PTA上<5-2 jmu-Java-02基本语法-02-StringBuilder>一题中用到了"str.appen ...

  7. dbda数据库类

    <?phpclass DBDA{ public $host="localhost";//服务器地址 public $uid="root";//用户名 pu ...

  8. dd命令

    前言 dd命令文件处理 dd命令用于复制文件并对原文件的内容进行转换和格式化处理.dd命令功能很强大的,对于一些比较底层的问题,使用dd命令往往可以得到出人意料的效果. dd命令 转换拷贝一个文件,特 ...

  9. Maven实战1

    屏上得来终觉浅,绝知此事要躬行 总结: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; c ...

  10. 深入理解计算机系统chapter6

    1. 2. 3. 存储器山