wink's

梦想总比现实闪耀,所以我一路追寻

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

volatile 和 LockingPtr(原创):

作者: wink

声明: 此文章允许被转载, 但是请尊重作者, 请注明出处.谢谢

 

  众所周知, 我不爱写文章, 但是今天确实很有冲动写这篇文章, 望各位看官一定注意. 下面的内容对大家多线程安全编程必有好处.

  写过多线程程序的人都知道, 写出多线程安全的代码, 而且要优雅的代码, 是很困难的事. 特别是当使用STL的时候, 经常会忘记加锁, 这个问题不可谓不烦人.

  下面说一种机制, 可以让编译器告知你, 你的代码有线程安全问题..听起来多么惬意的一件事情啊...是的, 本篇文章的主角就是大名鼎鼎的volatile...

  先说说volatile的知识.

class Gadget
{
public:
    void Foo() volatile;
    void Bar();
    ...
private:
    String name_;
    int state_;
};
Gadget regularGadget;
volatile Gadget volatileGadget;

 

当你调用
volatileGadget.Foo();   // 没有问题
 
regularGadget.Foo();   // 没有问题
volatileGadget.Bar(); // 编译出错, volatile的对象不可以调用非volatile的成员函数 

该怎么办呢, 我们都知道const和volatile是对立的, 这里可以用强制转型, 把对象的volatile去掉

Gadget& ref = const_cast<Gadget&>(volatileGadget);
ref.Bar();  // 这样是可以的

或许你觉得, 对一个对象用volatile, 很怪异, 这样到底对我们的代码到底有什么好处呢.

大家继续耐心的看下去. 

比如我们在类里面使用了stl, 其实任何一个程序员都不敢保证自己一定在使用它的时候加了锁, 这个时候, 如果我们对这个stl对象加上volatile关键字的话. 马上就会见分晓

我们先实现一个平台独立的Mutex类(在windows下就是关键区CRITICAL_SECTION) 

class Mutex
{
public:
    void Acquire();
    void Release();
    ...    
};
class SyncBuf {
public:
    void Thread1();
    void Thread2();
private:
    typedef vector<char> BufT;
    volatile BufT buffer_;  // 注意这里!!
    Mutex mtx_; // 用于同步BufT
};

当你在函数中 使用buffer_ 而不加锁的时候. 编译器会很优雅的告诉你. 你出错了

void SyncBuf::Thread2() {
    BufT::iterator i = buffer_.begin(); // 出错, 因为volatile对象buffer_不能访问非volatile成员函数, 而begin不是一个volatile成员函数
    for (; i != lpBuf->end(); ++i) {
        ... use *i ...
    }
}

感觉到很优雅了吗? 那么, 这个时候, 我们该如何方便的使用stl的buffer_呢? 

 

 

下面, 另一个主角要出场了 . 我们实现LockingPtr

template <typename T>
class LockingPtr {
public:
   LockingPtr(volatile T& obj, Mutex& mtx)
       : pObj_(const_cast<T*>(&obj)),  // 注意这里的const_cast<T*>, 关键!!
        pMtx_(&mtx)
   {    mtx.Acquire();    }
   ~LockingPtr()
   {    pMtx_->Release();    }
   // Ptr
   T& operator*()
   {    return *pObj_;    }
   T* operator->()
   {   return pObj_;   }
private:
   T* pObj_;
   Mutex* pMtx_;
   LockingPtr(const LockingPtr&);
   LockingPtr& operator=(const LockingPtr&);
};

现在, 我们可以在代码中既方便, 又优雅的使用STL了.

void SyncBuf::Thread1() {
    LockingPtr<BufT> lpBuf(buffer_, mtx_);
    BufT::iterator i = lpBuf->begin();
    for (; i != lpBuf->end(); ++i) {
        ... use *i ...
    }
}

看到了没有, 上面是不会出错的, 而且不失方便性.

 

总结:

  你应该在任何多线程共享的对象或者变量前面加上volatile关键字, 这是多线程编程的好帮手.

   其实volatile不止这个作用的, 有时候编译器为了优化, 把变量直接用寄存器保存, 那么这个时候, 其他线程如果改变了变量的话, 当前线程是根本不会发觉的. 加上了volatile的话, 就不存在这个问题了

    

 

 

 

 

 

 

  

posted on 2012-07-30 02:33  wink's  阅读(456)  评论(0编辑  收藏  举报