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的话, 就不存在这个问题了