volatileタイプ修飾子の使用
CおよびC++標準はスレッドに対して明らかな「沈黙を保つ」が、volatileキーワードの形で、マルチスレッドに少しの特権を残している.
皆さんがよく知っているconstのように、volatileはタイプ修飾子です.これは、異なるスレッドによってアクセスおよび変更される変数を修飾するように設計されています.volatileがない場合、マルチスレッドプログラムを記述できないか、コンパイラが最適化の機会を失うかのような結果になります.以下、一つ一つ説明します.
次のコードを考慮します.
上のコードのGadget::Waitの目的は1秒ごとにflagをチェックすることです.メンバー変数、flag_別のスレッドにtrueを設定すると、関数が返されます.少なくともこれはプログラム作成者の意図であるが,このWait関数は誤りである.
コンパイラはSleep(1000)が外部のライブラリ関数を呼び出していることを発見し、メンバー変数flag_を変更しないと仮定します.コンパイラはflagをレジスタにキャッシュされ、遅いマザーボードのメモリにアクセスする代わりに後でアクセスできます.これは単一スレッドコードにとって良い最適化ですが、この場合、GadgetのWait関数を呼び出すと、別のスレッドがWakeupを呼び出すと、Waitは常にループします.これはflag_の変更はキャッシュされたレジスタに反映されません.コンパイラの最適化は少し楽観的すぎる.
ほとんどの場合、変数をレジスタにキャッシュすることは非常に価値のある最適化方法であり、使わなければ残念です.CとC++は、このキャッシュ最適化を明示的に無効にする機会を提供します.変数がvolatile修飾子を使用していると宣言すると、コンパイラはこの変数をレジスタにキャッシュしません.アクセスするたびに変数のメモリ内の実際の位置にアクセスします.これでGadgetのWait/Wakeupを修正するのはflag_正しい修飾を加える:
volatileの原理と使い方に関する多くの解釈はここまでで、volatileで複数のスレッドで使用される原生タイプ変数を修飾することをお勧めします.しかし、volatileは不思議なC++タイプシステムの一部であるため、より多くのことをすることができます.
volatileをカスタムタイプに使用する
volatile修飾は、オリジナルタイプだけでなく、カスタムタイプにも使用できます.この場合、volatileの修飾はconstに似ています(constとvolatileを同時に使用することもできます).
constとは異なり、volatileの役割はオリジナルタイプとカスタムタイプに違います.すなわち、オリジナルタイプにvolatile修飾がある場合、それらの様々な操作(加算、乗算、付与など)は依然としてサポートされているが、classにとってはそうではない.たとえば、volatile以外のintの値をvolatileのintに割り当てることができますが、volatile以外のオブジェクトをvolatileオブジェクトに割り当てることはできません.
皆さんがよく知っているconstのように、volatileはタイプ修飾子です.これは、異なるスレッドによってアクセスおよび変更される変数を修飾するように設計されています.volatileがない場合、マルチスレッドプログラムを記述できないか、コンパイラが最適化の機会を失うかのような結果になります.以下、一つ一つ説明します.
次のコードを考慮します.
class Gadget
{
public:
void Wait()
{
while (!flag_)
{
Sleep(1000); // sleeps for 1000 milliseconds
}
}
void Wakeup()
{
flag_ = true;
}
...
private:
bool flag_;
};
上のコードのGadget::Waitの目的は1秒ごとにflagをチェックすることです.メンバー変数、flag_別のスレッドにtrueを設定すると、関数が返されます.少なくともこれはプログラム作成者の意図であるが,このWait関数は誤りである.
コンパイラはSleep(1000)が外部のライブラリ関数を呼び出していることを発見し、メンバー変数flag_を変更しないと仮定します.コンパイラはflagをレジスタにキャッシュされ、遅いマザーボードのメモリにアクセスする代わりに後でアクセスできます.これは単一スレッドコードにとって良い最適化ですが、この場合、GadgetのWait関数を呼び出すと、別のスレッドがWakeupを呼び出すと、Waitは常にループします.これはflag_の変更はキャッシュされたレジスタに反映されません.コンパイラの最適化は少し楽観的すぎる.
ほとんどの場合、変数をレジスタにキャッシュすることは非常に価値のある最適化方法であり、使わなければ残念です.CとC++は、このキャッシュ最適化を明示的に無効にする機会を提供します.変数がvolatile修飾子を使用していると宣言すると、コンパイラはこの変数をレジスタにキャッシュしません.アクセスするたびに変数のメモリ内の実際の位置にアクセスします.これでGadgetのWait/Wakeupを修正するのはflag_正しい修飾を加える:
class Gadget
{
public:
... as above ...
private:
volatile bool flag_;
};
volatileの原理と使い方に関する多くの解釈はここまでで、volatileで複数のスレッドで使用される原生タイプ変数を修飾することをお勧めします.しかし、volatileは不思議なC++タイプシステムの一部であるため、より多くのことをすることができます.
volatileをカスタムタイプに使用する
volatile修飾は、オリジナルタイプだけでなく、カスタムタイプにも使用できます.この場合、volatileの修飾はconstに似ています(constとvolatileを同時に使用することもできます).
constとは異なり、volatileの役割はオリジナルタイプとカスタムタイプに違います.すなわち、オリジナルタイプにvolatile修飾がある場合、それらの様々な操作(加算、乗算、付与など)は依然としてサポートされているが、classにとってはそうではない.たとえば、volatile以外のintの値をvolatileのintに割り当てることができますが、volatile以外のオブジェクトをvolatileオブジェクトに割り当てることはできません.