スレッドセーフ

出典: フリー百科事典『ウィキペディア(Wikipedia)』

スレッドセーフ: thread-safe)は、マルチスレッドプログラミングにおける概念である。あるプログラムコードがスレッドセーフであるという場合、そのコードを複数のスレッドが同時並行的に実行しても問題が発生しないことを意味する。特に、ある共有データへの複数のスレッドによるアクセスがあるとき、一度に1つのスレッドのみがその共有データにアクセスするようにして安全性を確保しなければならない。スレッドセーフでないコードを同時並行的に実行すると、レースコンディション(競合状態)が発生し、未定義動作を引き起こす。場合によっては深刻なセキュリティホール(脆弱性)が引き起こされることもある[1]

スレッドセーフはマルチスレッドプログラミングにおける重要な要素である。それは従来、オペレーティングシステムの開発者だけが考慮しなければならない問題だったが、1990年代後半には一般的な問題となった。マルチスレッドプログラムでは、複数のスレッドが同じアドレス空間内で同時に実行される。各スレッドのアクセスするメモリ領域が特に制限されることはなく、全スレッドが全アドレス空間にアクセスできる。従って、プログラムを見たときに予測される制御の流れやデータアクセスの順序は実際に実行時に起きることとかけ離れたものとなることが多い。これは「驚き最小の原則」に反している。スレッドセーフは実際の処理の流れとプログラムのソースコードの関連性を強化し、予期しない動作を最小にすることを指向した特性である。

スレッドセーフかどうかの判断基準[編集]

あるコードの断片がスレッドセーフかどうかを判断するのは簡単ではない。しかし、以下のような点に注意して調べることで問題が見つかることが多い。

スタック上の変数のみを使用し、引数にのみ依存し、同様な特性のサブルーチンしか呼ばないならば、そのサブルーチンはリエントラントであり、スレッドセーフである。このようなサブルーチンは「純関数; pure function」などと呼ばれることもあり、数学の関数によく似ている。

静的コード解析ツールの中には、プログラムの並行性に関するバグをある程度検出してくれるものもあり、スレッドセーフでないコードに対して警告を出す[2]

スレッドセーフの実現手法[編集]

スレッドセーフを実現する方法として以下のようなものがある。

リエントラント
リエントラント化することでスレッドセーフを実現できるが、広域変数などを使った状態情報のセーブができない。
相互排他
共有データへのアクセスをシリアライズ(逐次化)することでスレッドセーフを実現する。ただし、複数の共有データにアクセスする際には十分に注意しなければならない(排他制御参照)。複数のスレッドがお互いに異なるリソースをロックし合うと、デッドロックが発生することがある。
スレッドローカルデータ
例えばスレッドの識別子(番号)をキーとして広域変数をスレッドごとに持たせることで、サブルーチンを超えた範囲で変数を保持できるようにする。各変数にアクセスするサブルーチン自体はリエントラントではないが、特定のスレッドだけが特定の広域変数にアクセスすることが保証できれば、スレッドセーフとなる。
アトミック操作
共有データを何らかのアトミック(不可分)な操作でアクセスすることで他のスレッドから同時アクセスされないことを保証する。これは一般に特別な命令を必要とするが、そのようなハードウェア的な支援を必要としない純粋なソフトウェア的な解としてランポートのパン屋のアルゴリズムのように、ライブラリがそのような機能をサポートしている場合がある。アトミック操作は多くの排他機構の基盤となっている。

一般的にはこれらの手法に以下の手法を結合して使用する。

  • 共有データのスレッド固有のコピーを使用し、そのコピーの値で共有データをアトミックにアップデートする。このようにすることでコードの大部分は並行して実行可能となり、必要最小限の部分だけがシリアライズされる。

なお、ファイルのようなシステム共有リソースに関しては、同じプロセス内で動作する他のスレッドだけでなく、別のプロセス内で動作する他のスレッドからもアクセスされる可能性がある。そのため、場合によっては単にスレッドセーフにするだけでは不十分であり、必要に応じてプロセス間で排他制御する(ファイルロックも参照)。

脚注[編集]

注釈[編集]

  1. ^ JavaC#にはグローバル変数は存在しないが、静的フィールドが該当する。

出典[編集]

関連項目[編集]

外部リンク[編集]

以下、英文