yohhoyの日記

技術的メモをしていきたい日記

C++11標準ライブラリのスレッド安全性

C++11標準ライブラリが提供する機能とスレッド安全性(thread safety)についてのメモ*1C++03以前では処理系依存

要約C++標準ライブラリのオブジェクトでは、少なくとも言語組み込みint型と同様のスレッド安全性を提供する。

基本的なスレッド安全性

C++標準ライブラリが提供するオブジェクトは、個々の関数動作仕様で特記されるケースを除いて、下記の基本的なスレッド安全性を保証する。

  • 異なるスレッド上における異なるオブジェクトに対する操作はデータ競合を引き起こさない。*2(1.10, 17.6.5.9/p2-4)
  • 異なるスレッド上における同一オブジェクトに対する操作は、全スレッドがreadアクセスのみであればデータ競合を引き起こさない。少なくとも一方が変更操作を行う場合はデータ競合を引き起こす。*3 *4(1.10)
  • 明に要求している場合を除いて、引数やコンテナ要素が間接的に指すオブジェクトにアクセスしない。*5(17.6.5.9/p5)
  • コンテナ/文字列から取得したイテレータ上での操作は、指しているコンテナ/文字列オブジェクトへアクセスする可能性はあるが、コンテナ/文字列オブジェクトを変更することはない。例:イテレータのコピー操作によってその所属コンテナを変更することはない。*6(17.6.5.9/p6)
  • オブジェクトがスレッド間共有される内部状態を持つ場合でも、データ競合が生じないことを保証する。例:スレッド毎のstd::shared_ptrオブジェクトが同一オブジェクトを指しているとしても、各スレッド上で行われるstd::shared_ptrオブジェクト操作を安全に行える。*7(17.6.5.9/p7)
  • 一部のオブジェクトでは、スレッド安全性に関するより強い保証をする。例:std::mutexstd::atomic<T>等の一部メンバ関数。(17.6.5.9/p1)

N3337 17.6.5.9/p1-7より引用。

1 This section specifies requirements that implementations shall meet to prevent data races (1.10). Every standard library function shall meet each requirement unless otherwise specified. Implementations may prevent data races in cases other than those specified below.
2 A C++ standard library function shall not directly or indirectly access objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's arguments, including this.
3 A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's non-const arguments, including this.
4 [Note: This means, for example, that implementations can't use a static object for internal purposes without synchronization because it could cause a data race even in programs that do not explicitly share objects between threads. -- end note]
5 A C++ standard library function shall not access objects indirectly accessible via its arguments or via elements of its container arguments except by invoking functions required by its specification on those container elements.
6 Operations on iterators obtained by calling a standard library container or string member function may access the underlying container, but shall not modify it. [Note: In particular, container operations that invalidate iterators conflict with operations on iterators associated with that container. -- end note]
7 Implementations may share their own internal objects between threads if the objects are not visible to users and are protected against data races.

コンテナのスレッド安全性

C++標準ライブラリが提供するコンテナは、下記のスレッド安全性を保証する。

  • 次のメンバ関数はコンテナを変更しない:begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, at、連想/非順序連想コンテナ以外のoperator[]*8(23.2.2/p1)
  • 同一コンテナ中の異なる要素に対する変更はデータ競合を引き起こさない。ただしvector<bool>は除く。(23.2.2/p2)

N3337 23.2.2/p1-2より引用。

1 For purposes of avoiding data races (17.6.5.9), implementations shall consider the following functions to be const: begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, at and, except in associative or unordered associative containers, operator[].
2 Notwithstanding (17.6.5.9), implementations are required to avoid data races when the contents of the contained object in different elements in the same sequence, excepting vector<bool>, are modified concurrently.

スレッド安全でない関数

下記の標準ライブラリ関数またはオブジェクトは、常に(または指定引数が特定の条件を満たすとき)基本的なスレッド安全性を満たさない可能性があると明記されている*9

  • C標準ライブラリ関数の一部
    • asctime, ctime, gmtime, localtime(20.11.8/p2)
    • strerror, strtok(21.7/p14)
    • mbrlen, mbrtowc, mbsrtowc, mbtowc, wcrtomb, wcsrtomb, wctomb[引数依存](21.7/p15)
    • setlocale*10 と fprintf, fscanf, is(w)xxx系, localeconv, mblen, mbstowcs, mbtowc, (str|wcs)coll, (str|wcs)tod, (str|wcs)xfrm, to(w)(lower|upper), wcstombs, wctomb(18.10/p6, 22.6/p3)
    • rand[実装依存*11(26.8/p5)
    • tmpnam[引数依存](27.9.2/p2)
  • std::localeクラス(22.3.1/p9)
    • “プログラム全体で単一localeオブジェクト” または “スレッド毎のlocaleオブジェクト” がそれぞれ存在するか否かは処理系定義。処理系は必ず後者を提供しなければならず(こちらはスレッド毎なので無条件にスレッド安全性が保証される)、前者では該当localeオブジェクトに対する基本的なスレッド安全性を保証する義務がない。

N3337 22.3.1/p9より引用。

Whether there is one global locale object for the entire program or one global locale object per thread is implementation-defined. Implementations should provide one global locale object per thread. If there is a single global locale object for the entire program, implementations are not required to avoid data races on it (17.6.5.9).

関連URL

*1:余談:複数スレッドからのアクセスに対する安全性は真/偽のように二分論で定義されるものではないため、個人的には “スレッドセーフ” や “スレッド安全性” といった総称的な単語の利用は慎重であるべきと考えている。

*2:「スレッドAにおけるオブジェクトo1の操作と、スレッドBにおけるオブジェクトo2の操作を同時に行っても安全」という一般的に期待される保証を与える。

*3:普通のスカラ型(intなど)に対する保証と同じ。例:同一 std::map オブジェクトに対する複数スレッドからの同時readアクセスは安全に行える。1スレッドでも変更操作を行う場合はNG。後者のケースでは全 read / write 操作を mutex で保護する必要がある。

*4:コンストラクタやデストラクタは変更操作の一種であり、std::mutex オブジェクトなどの同期プリミティブであってもスレッド安全性を保証しないことに注意。(30.4.1.2/p5)

*5:「関数引数に指定したオブジェクトがさらに指す先のオブジェクトへ勝手にアクセスしない」という保証。

*6:あくまでイテレータオブジェクトそのものに対する操作を対象としている。「イテレータを介したコンテナ/文字列オブジェクトの変更操作」では当然データ競合を生じうることに注意。

*7:例えば std::shared_ptr が参照カウンタ方式で内部実装されていた場合、処理系はその参照カウンタ更新をデータ競合なしに行う義務がある。

*8:連想(associative)コンテナ:std::map。非順序連想(unordered associative)コンテナ:std::unordered_map。これらのコンテナの operator[] 操作では “キーが存在無ければ新要素を追加する” という変更が発生する。

*9:C++標準規格上では "may introduce a data race" のような表現になっている。処理系によってはスレッド安全なライブラリ関数を提供するかもしれない。

*10:22.6/p3:"Calls to the function setlocale may introduce a data race (17.6.5.9) with other calls to setlocale or with calls to the functions listed in Table 94."

*11:"It is implementation-defined whether the rand function may introduce data races (17.6.5.9). [Note: The random number generation (26.5) facilities in this standard are often preferable to rand. --end note]"