`
灵动的水
  • 浏览: 194578 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

volatile的介绍

阅读更多
volatile的介绍
      volatile类似于大家所熟知的const也是一个类型修饰符。volatile是给编译器的指示来说明对它所修饰的对象不应该执行优化。volatile的作用就是用来进行多线程编程。在单线程中那就是只能起到限制编译器优化的作用。所以单线程的童鞋们就不用浪费精力看下面的了。

没有volatile的结果
      如果没有volatile,你将无法在多线程中并行使用到基本变量。下面举一个我开发项目的实例(这个实例采用的是C#语言但不妨碍我们讨论C++)。在学校的一个.Net项目的开发中,我曾经在多线程监控中用到过一个基本变量Int32型的,我用它来控制多线程中监控的一个条件。考虑到基本变量是编译器自带的而且无法用lock锁上,我想当然的以为是原子操作不会有多线程的问题,可实际运行后发现程序的运行有时正常有时异常,改为用Dictionary对象处理并加锁以后才彻底正常。现在想来应该是多线程同时操作该变量了,具体的将在下面说清。

volatile的作用
      如果一个基本变量被volatile修饰,编译器将不会把它保存到寄存器中,而是每一次都去访问内存中实际保存该变量的位置上。这一点就避免了没有volatile修饰的变量在多线程的读写中所产生的由于编译器优化所导致的灾难性问题。所以多线程中必须要共享的基本变量一定要加上volatile修饰符。当然了,volatile还能让你在编译时期捕捉到非线程安全的代码。我在下面还会介绍一位大牛使用智能指针来顺序化共享区代码的方法,在此对其表示感谢。

      泛型编程中曾经说过编写异常安全的代码是很困难的,可是相比起多线程编程的困难来说这就太小儿科了。多线程编程中你需要证明它正确,需要去反复地枯燥地调试并修复,当然了,资源竞争也是必须注意的,最可恨的是,有时候编译器也会给你点颜色看看。。。

class Student
{
public:
    void Wait() //在北航排队等吃饭实在是很痛苦的事情。。。
    {
        while (!flag)
        {
            Sleep(1000); // sleeps for 1000 milliseconds
        }
    }
    void eat()
    {
        flag = true;
    }
    ...
private:
    bool flag;
};
      好吧,多线程中你就等着吃饭吧,可在这个地方估计你是永远等不到了,因为flag被编译器放到寄存器中去了,哪怕在你前面的那位童鞋告诉你flag=true了,可你就好像瞎了眼看不到这些了。这么诡异的情况的发生时因为你所用到的判断值是之前保存到寄存器中的,这样原来的地址上的flag值更改了你也没有获取。该怎么办呢?对了,改成volatile就解决了。

      volatile对基本类型和对用户自定义类型的使用与const有区别,比如你可以把基本类型的non-volatile赋值给volatile,但不能把用户自定义类型的non-volatile赋值给volatile,而const都是可以的。还有一个区别就是编译器自动合成的复制控制不适用于volatile对象,因为合成的复制控制成员接收const形参,而这些形参又是对类类型的const引用,但是不能将volatile对象传递给普通引用或const引用。

如何在多线程中使用好volatile
      在多线程中,我们可以利用锁的机制来保护好资源临界区。在临界区的外面操作共享变量则需要volatile,在临界区的里面则non-volatile了。我们需要一个工具类LockingPtr来保存mutex的采集和volatile的利用const_cast的转换(通过const_cast来进行volatile的转换)。

      首先我们声明一个LockingPtr中要用到的Mutex类的框架:

class Mutex
{
public:
    void Acquire();
    void Release();
    ...   
};
      接着声明最重要的LockingPtr模板类:

template <typename T>
class LockingPtr {
public:
   // Constructors/destructors
   LockingPtr(volatile T& obj, Mutex& mtx)
       : pObj_(const_cast<T*>(&obj)),
        pMtx_(&mtx)
   {    mtx.Lock();    }
   ~LockingPtr()
   {    pMtx_->Unlock();    }
   // Pointer behavior
   T& operator*()
   {    return *pObj_;    }
   T* operator->()
   {   return pObj_;   }
private:
   T* pObj_;
   Mutex* pMtx_;
   LockingPtr(const LockingPtr&);
   LockingPtr& operator=(const LockingPtr&);
};
      尽管这个类看起来简单,但是它在编写争取的多线程程序中非常的有用。你可以通过对它的使用来使得对多线程中共享的对象的操作就好像对volatile修饰的基本变量一样简单而且从不会使用到const_cast。下面来给一个例子:

       假设有两个线程共享一个vector<char>对象:

class SyncBuf {
public:
    void Thread1();
    void Thread2();
private:
    typedef vector<char> BufT;
    volatile BufT buffer_;
    Mutex mtx_; // controls access to buffer_
};
      在函数Thread1中,你通过lockingPtr<BufT>来控制访问buffer_成员变量:

void SyncBuf::Thread1() {
    LockingPtr<BufT> lpBuf(buffer_, mtx_);
    BufT::iterator i = lpBuf->begin();
    for (; i != lpBuf->end(); ++i) {
        ... use *i ...
    }
}
      这个代码很容易编写和理解。只要你需要用到buffer_你必须创建一个lockingPtr<BufT>指针来指向它,并且一旦你这么做了,你就获得了容器vector的整个接口。而且你一旦犯错,编译器就会指出来:

void SyncBuf::Thread2() {
    // Error! Cannot access 'begin' for a volatile object
    BufT::iterator i = buffer_.begin();
    // Error! Cannot access 'end' for a volatile object
    for (; i != lpBuf->end(); ++i) {
        ... use *i ...
    }
}
      这样的话你就只有通过const_cast或LockingPtr来访问成员函数和变量了。这两个方法的不同之处在于后者提供了顺序的方法来实现而前者是通过转换为volatile来实现。LockingPtr是相当好理解的,如果你需要调用一个函数,你就创建一个未命名的暂时的LockingPtr对象并直接使用:

unsigned int SyncBuf::Size() {
    return LockingPtr<BufT>(buffer_, mtx_)->size();
}
LockingPtr在基本类型中的使用
      在上面我们分别介绍了使用volatile来保护对象的意外访问和使用LockingPtr来提供简单高效的多线程代码。现在来讨论比较常见的多线程处理共享基本类型的一种情况:

class Counter
{
public:
    ...
    void Increment() { ++ctr_; }
    void Decrement() { —-ctr_; }
private:
    int ctr_;
};
      这个时候可能大家都能看出来问题所在了。1.ctr_需要是volatile型。2.即便是++ctr_或--ctr_,这在处理中仍是需要三个原子操作的(Read-Modify-Write)。基于上述两点,这个类在多线程中会有问题。现在我们就来利用LockingPtr来解决:

class Counter
{
public:
    ...
    void Increment() { ++*LockingPtr<int>(ctr_, mtx_); }
    void Decrement() { —?*LockingPtr<int>(ctr_, mtx_); }
private:
    volatile int ctr_;
    Mutex mtx_;
};

volatile成员函数
      关于类的话,首先如果类是volatile则里面的成员都是volatile的。其次要将成员函数声明为volatile则同const一样在函数最后声明即可。当你设计一个类的时候,你声明的那些volatile成员函数是线程安全的,所以那些随时可能被调用的函数应该声明为volatile。考虑到volatile等于线程安全代码和非临界区;non-volatile等于单线程场景和在临界区之中。我们可以利用这个做一个函数的volatile的重载来在线程安全和速度优先中做一个取舍。具体的实现此处就略去了。

总结
      在编写多线程程序中使用volatile的关键四点:

      1.将所有的共享对象声明为volatile;

      2.不要将volatile直接作用于基本类型;

      3.当定义了共享类的时候,用volatile成员函数来保证线程安全;

      4.多多理解和使用volatile和LockingPtr!(强烈建议)
分享到:
评论

相关推荐

    static,const,volatile用法

    下面将详细介绍这三个关键字的功能与用法。 #### 1. `static` `static` 关键字可以用来修饰变量和函数,它主要影响变量的作用域和生命周期。`static` 可以分为两种类型:全局作用域内的静态变量和局部作用域内的...

    extern_volatile等修饰符的用法

    本文将详细介绍这些修饰符的用法和特点。 一、const修饰符 const修饰符用于声明常量,可以修饰变量、指针、函数参数和返回值等。const修饰的变量不能被修改,是只读的。 1. 使用const声明常量 const修饰的变量...

    const,extern,static,volatile的使用

    ### const、extern、static、volatile ...通过以上的介绍可以看出,`const`、`extern`、`static` 和 `volatile` 这四个关键字在 C 和 C++ 中有着广泛的应用。正确地使用这些关键字可以极大地提高代码的质量和可维护性。

    Const与Volatile

    详细介绍Const与Volatile的相同与不同

    volatile详解

    详细介绍关键字volatile的作用,用法,意义,在c语言中的地位,及其与static的区别,

    const extern static volatile 小结

    ### const extern static volatile 小结 ...以上是对 `const`、`extern`、`static` 和 `volatile` 四个关键字的详细介绍。在实际开发过程中,根据具体情况选择合适的关键字来优化代码结构、提高程序效率是非常重要的。

    Java并发编程之volatile变量介绍

    Java并发编程中的volatile关键字是一个非常重要的概念,它用于处理多线程环境下的数据同步问题。在Java中,线程有自己的工作内存,它们可能不会立即看到其他线程对共享变量的修改,这可能导致数据的不一致性和并发...

    【C语言】Volatile的陷阱

    C语言volatile关键字的陷阱和注意事项 本文将从 volatile 关键字的使用陷阱和注意事项入手,详细解释 ...本文详细介绍了 volatile 关键字的使用注意事项和陷阱,以帮助开发者更好地理解和使用 volatile 关键字。

    Java多线程 volatile关键字详解

    Java多线程volatile关键字详解主要介绍了Java多线程volatile关键字的应用和原理。volatile是一种轻量同步机制,可以确保变量的可见性和顺序性,但不保证原子性。 volatile关键字的作用 volatile关键字可以确保变量...

    java入门教程:数据类型_Java理论与实践如何正确使用Volatile变量.docx

    本文旨在详细介绍`volatile`变量的基本概念、使用场景以及潜在的限制。 #### Volatile变量的概念 `volatile`变量的主要特点在于其提供的可见性保证。当一个线程修改了某个`volatile`变量之后,该变化会立即对所有...

    详解Java面试官最爱问的volatile关键字

    本文将详细介绍volatile关键字的方方面面,包括其作用、原理和使用场景。 volatile关键字的作用: volatile关键字可以保证共享变量的内存可见性和禁止指令重排序。其中,内存可见性指的是当多个线程访问同一个共享...

    Java并发volatile可见性的验证实现

    本文将通过示例代码详细介绍Java并发volatile可见性的验证实现。 一、非volatile变量的可见性问题 在示例代码中,我们首先定义了一个非volatile变量flag,并在主线程中将其设置为true。在子线程中,我们使用while...

    位域和volatile

    下面将详细介绍位域的概念、作用及其在嵌入式开发中的具体应用。 ##### 1.1 位域概念 位域是C语言中一种特殊的数据结构,它允许开发者在单个整数类型变量中定义多个字段,每个字段占用固定的比特位(bit)。这种特性...

    __asm__ __volatile__内嵌汇编用法简述

     __asm__ __volatile__内嵌汇编用法简述 在阅读C/C++原码时经常会遇到内联汇编的情况,下面简要介绍下__asm__ __volatile__内嵌汇编用法。因为我们华清远见教学平台是ARM体系结构的,所以下面的示例都是用ARM汇编。...

    ISSCC2021_Session_30V_Non-Volatile Memory.pdf

    此次会议中发布了题为“A176-Stacked 512Gb 3b/Cell 3D-NAND Flash with 10.8Gb/mm2 Density Using Peripheral Circuit under Cell Array”的论文,介绍了由SK Hynix研发的512Gb的三维堆栈型NAND闪存技术,该技术...

    Java 关键字 volatile 的理解与正确使用

    本文主要介绍 volatile 的使用准则,以及使用过程中需注意的地方。 为什么使用 volatile? 1. 简易性:在某些需要同步的场景下,使用 volatile 变量要比使用锁更加简单。 2. 性能:在某些情况下,使用 volatile ...

    C++中mutable与volatile的深入理解

    下面话不多说了,来一起看看详细的介绍吧 mutable mutable只能作用在类成员上,指示其数据总是可变的。不能和const 同时修饰一个成员,但能配合使用:const修饰的方法中,mutable修饰的成员数据可以发生改变,除此...

    VLSI-Design of Non-Volatile Memories

    #### 非易失性存储器(Non-Volatile Memories)概述 非易失性存储器(Non-Volatile Memories, NVMs)是一种即使在电源关闭后也能保持数据的存储器类型。与易失性存储器(如RAM)不同,NVMs在断电后不会丢失信息,这...

    详解java并发编程(2) --Synchronized与Volatile区别

    下面我们将详细介绍Synchronized和Volatile的区别和使用。 Synchronized Synchronized是一种同步机制,它可以保证多个线程在同一时刻只能有一个线程处于方法或者同步块中。Synchronized可以修饰方法、代码块和类,...

Global site tag (gtag.js) - Google Analytics