POSIX
プログラミング
Pthreads編
デジタルビジョンソリューション㈱
中山
一弘
参考図書
Pthreadsプログラミング, Bradford
Nichols, Dick Buttlar, Jacqeline Proulx
Farrell, ISBN4-900900-66-4
Pthreads
POSIXスレッド標準を実装したライブラ
リを
Pthreadsと呼ぶ。
C言語のデータ型、関数、定数を定義。
プログラムをサブタスクに分割し、各サ
ブタスクを並列に、あるいはインターリ
ブして実行するという仕組みの標準化さ
れたモデル。
POSIX
IEEEが策定した各種UNIX OSを始めとす
る異なる
OSに共通のAPIを定めたアプリ
ケーションインタフェース規格
システムコール、ファイルとディレクトリ、
システムデータベースなど多岐に渡る。
単に
POSIXといった場合は、システムコー
ルとライブラリ関数を規定した
POSIX.1(IEEE Std 1003.1)を指す。
組み込みにおける
POSIX
世界中で最も普及している標準
OSのひと
つ、
POSIX仕様OSが、組込みシステムに
採用されるケースが増えてきている。
組込み
Linuxはその一例。
eSOL株式会社がT-Kernel/POSIXを発表。
この背景には、車載機器やデジタル家電、監視カメラなど組込
みシステムが、高度化および高機能化してきたことで開発する
ソフトウェア量が膨大になってきたこと、スタンドアロンの動
作ではなくネットワークを介した機器間の通信や連携動作が必
要になってきたことなどから、こうした課題を解決できる
POSIX仕様OSが注目されるようになってきた。
POSIXのメリット
ソフトウェア資産が豊富
GNUプロジェクトをはじめとしたPOSIX
仕様
OS上で動作するソフトウェアが多数
存在する。そうした
UNIX資産を活用する
ことにより、工数の削減につながる。
エンジニアリソースの確保が容易
世界中に
UNIXプログラム経験があるエン
ジニアが存在。逆に言うと、今後は組み込
み開発エンジニアに
UNIXプログラム経験
が求められるように。
プロセス
ユーザプログラムの実行を管理する基本
単位。
psコマンドで一覧表示可能。
OSによる資源管理の単位でもある。
CPU時間やメモリを割り当てる対象
ファイルなどの資源のアクセス権限があ
るかどうかの制御単位。
プロセスは互いに非干渉。
スレッド
プロセス内の独立したプログラム実行
プログラムカウンタやスタックといった実
行状態に関連する情報を収めている。
同一プロセス
ID。
スレッド同士は論理メモリ空間を共有。
スレッド関連の処理はプロセス関連の処
理よりもオーバーヘッドが少ない。
そのため軽量プロセスとも呼ばれることも。
プロセスとスレッド
スレッド
プロセス
オーバーヘッド
小
大
メモリ
共有
独立
保護
メモリを共有するた
め、意識して保護す
る必要あり
メモリが独立してい
るため、他プロセス
の資源は保護される
データ共有
メモリのポインタ渡
し
(コピー不要)
パイプ、ソケット、
ファイルなど
主要な同期方法
mutex、条件変数、
リーダ
/ライターロッ
ク
セマフォ、共有メモ
リ上の
mutex
RTOSでいう
タスク
は、
スレッド
にあたると言える。
複数のスレッドを持つプロセスとしてのプログラム
仮想アドレス空間 do_another_thing () ... do_one_thing () ... スタック スタック データ ヒープ SP PC ... レジスタ SP PC ... レジスタ スレッド リソース スレッドスレッドに有効な例
ほかのタスクから独立している。
タスクが、長時間の待ちでブロックされ
る可能性がある。
タスクは大量の
CPUサイクルを使う可能
性がある。
非同期イベントに応答する必要がある。
ある作業が、アプリケーション中の他の
作業よりも重要度が高い、あるいは低い。
スレッドが有効ではない例
前述のスレッドに有効な例に当てはまら
ないタスク。
逐次的な処理の高速化など。
O
逐次的な数値計算。
O
逐次的な
IO処理(ファイルの連続読込、連続
書込
)。
マルチスレッド処理はオーバヘッドとなる。
スレッドのデメリット
スレッドセーフな関数の考慮が必要
マルチスレッドではメモリを共有するため、
スレッドセーフ
マルチスレッドセーフ、
MTセーフ、reentrant(リエン
トラント、再入可能)ともいう
同時に複数のスレッドで呼出しても良い関数
下記を守っている場合スレッドセーフである。
関数内でstatic変数やグローバル変数の操作を行わない。 関数内で非スレッドセーフな関数を呼び出していない。 上記を守れない場合、その部分をmutexなどで同期化し、 他のスレッドが操作できないようにしている。 ほとんどの関数はスレッドセーフであるが、例外がある
並列性と並行性
マルチスレッドプロセスがシングルプロセッサ上で動作す
る場合は、プロセッサが実行リソースを各スレッドに順次
切り替えて割り当てるため、プロセスの実行状態は並行的
になります。
同じマルチスレッドプロセスが共有メモリー方式のマルチ
プロセッサ上で動作する場合は、プロセス中の各スレッド
が別のプロセッサ上で同時に走行するため、プロセスの実
行状態は並列的になります。
プロセスのスレッド数がプロセッサ数と等しいか、それ以
下であれば、スレッドをサポートするシステム
(スレッド
ライブラリ) とオペレーティング環境は、各スレッドがそ
れぞれ別のプロセッサ上で実行されることを保証します。
たとえば、スレッドとプロセッサが同数で行列の乗算を行
う場合は、各スレッド
(と各プロセッサ) が 1 つの行の計算
を担当します。
スレッドの生成
(1)
pthread_create()
int pthread_create(
pthread_t *thread ,
const pthread_attr_t *attr , void *(*start_routine)(void *) , void *arg ); attrで指定された属性を持つスレッドを生成する。attrが NULLの場合はデフォルトの属性が使われる。thread引数には、 新しいスレッドのスレッドハンドルが返される。新しいス レッドはstart_routineで実行を開始し、これに指定された引 数が一つ渡される。
スレッドの生成
(2)
ITRON系のRTOSとの違う点
cre_tsk()で生成後にsta_tsk()で起動する必
要があるが、
Pthreadsでは
pthread_create() のみ。
ITRON系のRTOSと同じ点
スタートルーチンに引数で一つ情報を渡せ
る。
構造体で渡せば複数の情報を渡せる。
スレッドの終了、終了待ち
pthread_exit()
void pthread_exit( void *value ); 呼び出し側のスレッドを終了し、事前にそのスレッドに関し てpthread_join()を呼び出していた任意のスレッドに指定され たvalueを返す。
pthread_join()
int pthread_join(pthread_t thread , void **value_ptr );
指定されたスレッドが終了するまで、呼び出し側スレッドを 待ち状態にする。value_ptr引数は、終了したスレッドの返り 値を受け取る。
データの競合
データ競合の定義
他のスレッドがアクセス可能な領域を、同時に
あるスレッドが修正してしまう可能性のある場
合、プログラムはデータ競合があるという。
共有リソース管理のための基本的な下記の
ルールを守る必要がある。
リソースにアクセスする前にロックを獲得する。
リソースに関する作業が完了したら、ロックを
解放する。
同期
スレッドは非同期で動作しますが、スレッド内の全処理
を非同期で処理する事は少なく、通常は仕様上および効
率性の理由から、多少なりともスレッドが参照する一部
の情報を他スレッドや呼び出し元と同期(他スレッドの
排除)する必要があります。
スレッドはプロセス内の変数を共有しているので、何も
対策をとらないと、あるスレッドが書き換えている最中
に他のスレッドが更新したり消した時に問題が発生する。
スレッド間で協調して管理しなくてはいけない情報にア
クセスするための機能を、
同期処理
と言う。
Pthreadsの同期
mutexロック
相互排他として働き、データに対するアクセス
を制御できる。
状態変数
スレッドが待つイベントに名前をつけ、他ス
レッドが待っている状態に達したことを通知す
る。
リーダー
/ライターロック
複数のスレッドが同時にデータを読み込むこと
を許可しながら、データを書き込むスレッドは
排他的なアクセスができることを保証する。
mutexロック
相互排他
あるスレッドがデータに排他的なアクセス
を行なっている時、他のスレッドは同じ
データに同時にアクセス出来ない。
mutexロックの使い方
mutexを作成し、初期化する。
リソースにアクセスする時、
pthread_mutex_lockを使ってリソースの
mutexをロックする。
リソースに関する作業を完了したら、
pthread_mutex_unlockを呼び出して
mutexのロックを解除する。
mutexの初期化
pthread_mutex_init
int pthread_mutex_init (
pthread_mutex_t *mutex ,
const pthread_mutexattr_t *attr );
指定されたmutex属性オブジェクトで定められる属性で、 mutexを初期化する。
mutexのロック
pthread_mutex_lock
int pthread_mutex_lock ( pthread_mutex_t *mutex ); ロックされていないmutexをロックする。すでにロックされ ている場合は、解放されるまで呼び出し側のスレッドはブ ロックされる。
pthread_mutex_trylock
int pthread_mutex_trylock ( pthread_mutex_t *mutex ); mutexのロックを試みる。すでにロックされている場合は、 呼び出し側のスレッドは解放されるのを待つことなくリター ンする。mutexのロック解除
pthread_mutex_unlock
int pthread_mutex_unlock ( pthread_mutex_t *mutex ); mutexのロックを解除する。どれが再開されるかは、スケ ジューリングプライオリティで決定される。状態変数
条件が満たされるまで待つことに利用。
基本操作
O(条件が満たされたときに)シグナルを送る。
O(条件が満たされていなければ)シグナルが送ら
れるのを待つ。
動作例
O一つ以上のスレッドが条件変数で待つ。
O条件変数にシグナルが送られたら、どれかのス
レッドが実行を開始。
誰も待っていない条件変数にシグナルが送られ
ても無視される
(状態を持たない)。
リーダー
/ライターロック(1)
データを複数のスレッドからアクセスする際に、すべて読み込み処理で あれば、問題なく並列に処理することができる。そのため、読み込みと 書き込み処理をきちんと区別して保護することで、読み込み処理のみ実 行している場合にはデータへの並列アクセスが可能となる。 この「読み込み処理と書き込み処理を区別して保護する」機構が Read/Write ロックとなる。 Read/Writeロックでは、以下のように動作する。 読み込みを保護する「読み込みロック」と書きこみを保護する「書き込 みロック」がある。 書き込みロックを保持するスレッドがある場合には、他のスレッドは読 み込みロック/書き込みロックのどちらの種類のロックも取得すること ができない。 読み込みロックを保持するスレッドがある場合には、他のスレッドは読 み込みロックのみ取得することができる。スレッド間の通信
POSIXではプロセス間の通信の手段とし
てメッセージキューが用意されているが、
スレッド間の通信用の
APIは存在しない。
RTOSでいうrcv_mbx(), snd_mbx()といっ
た通信手段がない。
スレッドプールという考え
(1)
都度、ワーカスレッドを生成するとス
レッド作成のオーバーヘッドがかかる。
ワーカスレッドを再利用することができ
スケジューリング
カーネルの一部で、次に CPU で実行される 実行可能なプロセ
スを決定するものである。 Linux のスケジューラーは三つの異
なるスケジューリング方針を提供しており、 一つは通常のプロ
セス用、二つはリアルタイム・アプリケーション用である。 全
てのプロセスには 静的優先度 (static priority) sched_priority が 割り当てられ、 この値はシステム・コールを通してのみ変更で きる。 sched_priority は 0 から 99 の範囲の値を取ることがで き、 スケジューラはその sched_priority の値それぞれに対し て 実行可能なプロセスのリストを管理している。次に実行する プロセスを 決定するために、Linux のスケジューラは静的優先 度の最も高い 空でないリストを探して、そのリストの先頭のプ ロセスを取り出す。 スケジューリング方針はそれぞれのプロセ スが同じ静的優先度を持つ プロセスのリストの中のどこに挿入 され、このリストの中をどのように 移動するかを決定する。