以前のいくつかの質問/回答で明確にされていますが、これはvolatile
マルチスレッドではなく、C++ メモリ モデルの可視状態に関連しています。
一方、Alexandrescu によるこの記事volatile
では、このキーワードをランタイム機能としてではなく、コンパイル時のチェックとして使用して、コンパイラがスレッド セーフではない可能性のあるコードを受け入れないように強制しています。この記事では、キーワードはrequired_thread_safety
、実際に意図されているvolatile
.
volatile
これは適切なの(乱用)使用ですか?このアプローチには、どのような落とし穴が隠されている可能性がありますか?
最初に頭に浮かぶのは、追加の混乱です。volatile
これはスレッド セーフとは関係ありませんが、より優れたツールがないため、それを受け入れることができました。
記事の基本的な簡略化:
variable を宣言すると、その変数volatile
に対してメンバー メソッドのみvolatile
を呼び出すことができるため、コンパイラは他のメソッドへのコードの呼び出しをブロックします。std::vector
インスタンスを as として宣言するvolatile
と、クラスのすべての使用がブロックされます。const_cast
要件を解放するために実行するロッキング ポインターの形でラッパーを追加すると、ロッキング ポインターをvolatile
介したすべてのアクセスが許可されます。
記事からの盗用:
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&);
};
class SyncBuf {
public:
void Thread1() {
LockingPtr<BufT> lpBuf(buffer_, mtx_);
BufT::iterator i = lpBuf->begin();
for (; i != lpBuf->end(); ++i) {
// ... use *i ...
}
}
void Thread2();
private:
typedef vector<char> BufT;
volatile BufT buffer_;
Mutex mtx_; // controls access to buffer_
};
ノート
最初のいくつかの回答が表示された後、最も適切な言葉を使用していない可能性があるため、明確にする必要があると思います。
の使用は、volatile
それが実行時に提供するもののためではなく、コンパイル時にそれが何を意味するかによるものです。つまり、const
キーワードがユーザー定義型でほとんど使用されていない場合、同じトリックをキーワードで引き出すことができますvolatile
。つまり、メンバー関数呼び出しをブロックできるキーワード (たまたま volatile と綴られている) があり、Alexandrescu はそれを使用して、コンパイラをだましてスレッドセーフでないコードのコンパイルに失敗させています。
コンパイル時に何をするかという理由ではなく、コンパイラに何をさせるかという理由でそこにある多くのメタプログラミングのトリックと私は考えています。