yohhoyの日記

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

「pthreadサポート」の意味するところ

ある処理系が “POSIXスレッド(pthread)標準をサポートする” とき、処理系(実行環境を含む)で担保すべき事項と、利用者(アプリケーションプログラマ)が守るべき制約についてメモ。

アプリケーションをプログラマの意図通り実行させるための、処理系/利用者間の責任分解点は「メモリ同期(memory synchronization)」により定義される。

  • 利用者:複数スレッドからの少なくともどれか1つが書込操作となる変数アクセスの場合、“メモリ同期関数” を用いた排他制御により変数アクセスが同時に生じないよう保証すること。
    • 全てが読込操作である変数アクセスの場合、複数スレッドからの変数アクセスを同時に行ってもよい。(そのようなコードを書いても、意図通り実行されることが保証されている。)
  • 処理系コンパイル時に最適化を行う場合でも、あるスレッド内での変数アクセス操作を “メモリ同期関数” 呼び出しをまたいで移動させないこと。同様に、プログラム実行時にOut-of-Order実行が行われる場合でも、“メモリ同期” ポイントをまたぐメモリアクセスが生じないように制御すること。
    • 後者はコンパイラが出力するアセンブラ命令列において、メモリ同期ポイントに対応する箇所にメモリバリア命令(相当)が出力されることを意味する。

POSIX規格(IEEE Std 1003.1-2004)より “メモリ同期関数” リストから一部引用。

4.11 Memory Synchronization
Applications shall ensure that access to any memory location by more than one thread of control (threads or processes) is restricted such that no thread of control can read or modify a memory location while another thread of control may be modifying it. Such access is restricted using functions that synchronize thread execution and also synchronize memory with respect to other threads. The following functions synchronize memory with respect to other threads:

  • pthread_create()
  • pthread_join()
  • pthread_mutex_lock()
  • pthread_mutex_timedlock()
  • pthread_mutex_trylock()
  • pthread_mutex_unlock()
  • pthread_cond_broadcast()
  • pthread_cond_signal()
  • pthread_cond_timedwait()
  • pthread_cond_wait()
  • pthread_barrier_wait()
  • sem_post()
  • sem_timedwait()
  • sem_trywait()
  • sem_wait()
  • (snip)

Applications may allow more than one thread of control to read a memory location simultaneously.

The Open Group Base Specifications Issue 7, 4. General Concepts

具体例1

ミューテックス型(pthread_mutex_t)を操作するpthread_mutex_lock/unlock関数は、POSIX規格により “メモリ同期” を行うと定義されている。処理系が下記コードをコンパイルするとき、最適化処理では「S1, S2をLockより前に移動」「S1, S2をUnlockより後ろに移動」といった処理順序入れ替えが禁止される。一方で「順番S1→S2からS2→S1へ入れ替え」といった最適化処理は許容される。*1

#include <pthread.h>
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
int data, ready;  // スレッド間で共有される変数

void func1()
{
  //...
  pthread_mutex_lock(&mtx);    // Lock
  data = some_calculation();   // S1
  ready = 1;                   // S2
  pthread_mutex_unlock(&mtx);  // Unlock
  //...
}

具体例2

現在のスレッドIDを取得するpthread_self関数は “メモリ同期” を行わない。このため下記コードの場合、コンパイラによる最適化処理にて「順番S1→S2からS2→S1へ入れ替え」を行ってもよい。

#include <pthread.h>
pthread_t id;
int state;
// func2関数の実行中に、他スレッドからは
// 変数id, stateに同時アクセスされないと仮定

void func2()
{
  //...
  id = pthread_self();  // S1
  state = 1;            // S2
  //...
}

関連URL

*1:本文中では「変数アクセス操作の移動」と「コンパイラによる最適化処理」の関係に言及していないが、冗長処理の削除/定数畳み込みや共通項の括りだしなどを目的として、コンパイラの最適化処理では「変数アクセス操作の移動」が行われる。移動の自由度が大きい方がより効率的なコードが生成される可能が高くなる。wikipedia:コンパイラ最適化 wikipedia:en:Compiler_optimization