标题:浅谈Volatile与多线程
2011-04-19 22:49:17
最近看的比较杂,摘了一些人的笔记!
随着多核的日益普及,越来越多的程序将通过多线程并行化的方式来提升性能。然而,编写正确的多线程程序一直是一件非常困的事情,volatile关键字的使用就是其中一个典型的例子。
C/C++中的volatile一般不能用于多线程同步
在C/C++中,如果想把一个变量声明为volatile,就相当于告诉编译器这个变量是“易变的”,他随时可能在其他地方被修改,所以编译器不能对其做任何变化:即每次读写该变量时都必须对其内存地址直接进行操作,并且所以对该变量的操作都必须严格按照程序中规定的顺序执行。举例来说,编译器的常常做的一种性能优化就是把需频繁读取的变量缓存到寄存器中,以提升访问速度。但如果该变量的值随时可能在片外被改变的话,那么就有可能出现被缓存的值并不是该变量的最新值情况,从而出现运行错误。在这种情况就需要用volatile关键字来修饰这个变量,以确保编译器不会对该变量读写操作进行任何缓存优化。另一个例子就是内存映射I/O操作。如下代码所示:
Int *p = get_io_address();
Int a, b;
A = *p;
B = *p;
P是一个指向硬件I/O端口的指针,该端口的值在每进行一次读操作后都会变化。这个程序连续对该端口进行两次读取操作已将两个不同的值分别赋值给a和b。如果不把a和b声明为volatile的话,编译器可能会”自作聪明”地认为两次从p读取的值都是一样的,从而把*b=*p优化成b = a,最终导致程序出错。
虽然C/C++中volatile关键字对这种“易变“的读写操作能起到一定的保护,但他却并不适用于多线程程序中共享变量的同步操作。究其根源,就在于C/C++标准中并没有volatile赋予原子性和顺序性的语义。

原子性
下面举个例子说明原子性。i++这看似原子的语句其实有三个操作组成:将该值从内存地址读取到寄存器中,对寄存器中的值进行加1操作,最后再将新值写回内存中,正是因为i++并不是原子的,所以如果两个线程同时进行i++操作的话仍会产生数据竞跑,从而导致i的最终值不等于2.在这种情况下,C/C++中的volatile关键字根本无法对该操作的原子性提供任何保障。
Volatile int  i=0;
//线程1
I++;
//线程2
I++;
顺序性
不幸的是,现在C/C++标准中的volatile关键字对共享变量操作的顺序性也未提供任何保障。以本文中的dekker算法为例:当两个线程分别执行dekker1和dekker2函数时候,改程序通过对flag1/2和turn的读写来实现两个线程对临界区中共享变量gCounter的互斥访问。这个算法的关键就在于对flag1/2和turn的读写操作是在其写操作之后进行的,因此它能保证dekker1和dekker2中对gCounterde的操作时互斥的,相当于把gCounter++放到一个临界区中去了。Dekker算法如下所示:
Volatile int flag1 = 0;
Volatile int flag2 = 0;
Volatile int turn = 1;
Volatile int gCounter = 0;
Void dekker1()
{
       Flag1 = 1;
       Turn = 2;
While( (flag2 == 1) && ( turn == 2) ){}
//进入临界区
       gCounter++;
       flag1 = 0; //离开临界区
}
 
Void dekker2()
{
       Flag2 = 1;
       Turn = 2;
While( (flag1 == 1) && ( turn == 2) ){}
//进入临界区
       gCounter++;
       flag2 = 0; //离开临界区
}
 
尽管volatile规定编译器不能对同一变量的所有操作进行乱序优化,但它却不能阻止编译器对不同volatile变量间的操作进行乱序优化。例如,编译器可能把dekker1中的flag2读操作提到flag1和turn写操作之前,从而导致对临界区的互斥访问失效,最终gCounter++操作就会出现数据竞跑现象。事实上,即使编译器没有对这个程序做任何优化,volatile 关键字也不能阻止多核CPU对该程序的乱序优化。以常见的x86硬件来说,它可以对不同变量x,y的store x --àload y进行乱序优化,把load y操作提到store x操作之前。这样的话,dekker1中flag2的读操作还是有可能会被提到flag1和turn的写操作之前,最终导致错误的计算结果。
那为什么编译器和多核CPU会对多线程程序做这样的乱序优化呢?因为从单核的视角来看,flag1 和 flag2,turn的读写操作之间没有任何依赖关系的,使用编译器/CPU当然可以对他们进行乱序优化以隐藏一部分的内存访问延迟,从而更好的利用CPU里的流水线。换句话说,这样的优化虽从单线程的角度来讲没有错,但却违反了设计这个多线程算法时所期望的多线程语义。要是解决这个问题,我们需要解决这个问题,我们需要自己添加内存栅栏以显式保证顺序性,或者干脆去别去实现这样的算法,转而使用类似pthread_mutex_lock这样的加锁操作来实现互斥访问。
综合上述,由于现有的C/C++标准中并没有对volatile添加原子性和顺序性的语义,所以绝大部分C/C++程序中使用volatile来进行多线程同步的用法是错误的。其实,我们之所以想用volatile变量进行同步,无非是因为锁,条件变量等方式的开销太大,所以想有一种轻量级的,高效的同步机制。

浅谈Volatile与多线程的更多相关文章

  1. 浅谈volatile关键字

    volatile是一种轻量级的同步机制.它可以保证内存可见性以及防止指令重排序,但是不保证原子性 volatile和JMM机制是不可分割的,在谈volatile的时候有必要先了解以下JMM JMM(J ...

  2. 浅谈 volatile 的实现原理

    在并发编程中我们一般都会遇到这三个基本概念:原子性.可见性.有序性.我们稍微看下volatile 原子性 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行. ...

  3. 浅谈volatile与automicInteger

    在并发环境中有三个因素需要慎重考量,原子性.可见性.有序性.   voatile 保证了有序性(防止指令冲排序)和变量的内存可见性(每次都强制取主存数据),每次取到volatile变量一定是最新的  ...

  4. 【转】浅谈多核CPU、多线程、多进程

    浅谈多核CPU.多线程.多进程 1.CPU发展趋势 核心数目依旧会越来越多,依据摩尔定律,由于单个核心性能提升有着严重的瓶颈问题,普通的桌面PC有望在2017年末2018年初达到24核心(或者16核3 ...

  5. 浅谈原子操作、volatile、CPU执行顺序

    浅谈原子操作.volatile.CPU执行顺序 在计算机发展的鸿蒙年代,程序都是顺序执行,编译器也只是简单地翻译指令,随着硬件和软件的飞速增长,原来的工具和硬件渐渐地力不从心,也逐渐涌现出各路大神在原 ...

  6. 浅谈iOS多线程

    浅谈iOS多线程 首先,先看看进程和线程的概念. 图1.1 这一块不难理解,重点点下他们的几个重要区别: 1,地址空间和资源:进程可以申请和拥有系统资源,线程不行.资源进程间相互独立,同一进程的各线程 ...

  7. 浅谈C++11中的多线程(三)

    摘要 本篇文章围绕以下几个问题展开: 进程和线程的区别 何为并发?C++中如何解决并发问题?C++中多线程的基本操作 浅谈C++11中的多线程(一) - 唯有自己强大 - 博客园 (cnblogs.c ...

  8. 浅谈C++11中的多线程(二)

    摘要 本篇文章围绕以下几个问题展开: 进程和线程的区别 何为并发?C++中如何解决并发问题?C++中多线程的基本操作 浅谈C++11中的多线程(一) - 唯有自己强大 - 博客园 (cnblogs.c ...

  9. 浅谈C#更改令牌ChangeToken

    前言 在上篇文章浅谈C#取消令牌CancellationTokenSource一文中我们讲解了CancellationTokenSource,它的主要功能就是分发一个令牌,当我取消令牌我可以进行一些回 ...

随机推荐

  1. UFO长啥样?--Python数据分析来告诉你

    前言 真心讲,长这么大,还没有见过UFO长啥样,偶然看到美国UFO报告中心有关于UFO时间记录的详细信息,突然想分析下这些记录里都包含了那些有趣的信息,于是有了这次的分析过程. 当然,原始数据包含的记 ...

  2. Windows命令远程执行工具Winexe

    Windows命令远程执行工具Winexe   在对Windows系统执行渗透测试中,通过各种方式可以获取目标主机的用户名和密码.这时,只要对方主机开启文件共享服务,就可以借助Winexe工具远程执行 ...

  3. Electron与jQuery中$符号冲突的三种解决方法

    在Electron工程中引用jQuery时,经常会出现以下错误: Uncaught ReferenceError: $ is not defined 解决的具体方法如下: ①.在测试的过程中(测试过1 ...

  4. 初见Python<1>:基础语法

    1.两个整数相除,计算结果的小数部分被截除,结果仍然是一个整数: 如:1/2=0 2.整数和浮点数相除.或者浮点数之间相除,结果有小数部分,仍然是一个浮点数: 如:1/2.0=0.5  1.0/2=0 ...

  5. 【贪心】【堆】Gym - 101128C - Canvas Painting

    一些画布,每块有其大小,一开始都是白的,你任意将它们排序,然后一次操作可以选择一段连续的相同颜色的画布,从中任选一个位置,左侧涂上任意一种颜色,右侧涂上另一种.消耗是这一段画布的总的大小.问你要将所有 ...

  6. 【BFS】bzoj1054 [HAOI2008]移动玩具

    暴搜吧,可以哈希一下,但是懒得写哈希了,所以慢得要死. Code: #include<cstdio> #include<queue> #include<set> # ...

  7. Manthan, Codefest 16 B. A Trivial Problem 二分 数学

    B. A Trivial Problem 题目连接: http://www.codeforces.com/contest/633/problem/B Description Mr. Santa ask ...

  8. redis(一)Windows下安装redis服务、搭建redis主从复制

    接下来会写一个redis实战系列,在此记录,有什么问题大家请随时批评. 好了,进入正题,这篇会将redis以windows服务形式提供服务(搭建一个简单的主从复制 M:6379:s:6380.6381 ...

  9. 使用 Google Code Prettify 实现代码高亮

    今天这篇文章主要讲述使用 google-code-prettify 来实现代码的高亮显示,以前我使用 highlight.js 来实现文章中代码的高亮显示. prettify 非常小巧且配置简单,使用 ...

  10. springBoot单元测试-基础单元测试

    1)在pom文件中加入junit支持 <!-- spring-boot-starter-test 单元测试 --> <dependency> <groupId>or ...