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 でいうタスクは スレッドにあたると言える
複数のスレッドを持つプロセスとしてのプログラム 仮想アドレス空間 スレッド レジスタ SP PC... do_another_thing ()... スタック スレッド レジスタ SP PC... do_one_thing ()... スタック リソース データ ヒープ
スレッドに有効な例 ほかのタスクから独立している タスクが 長時間の待ちでブロックされる可能性がある タスクは大量の 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 のスケジューラは静的優先度の最も高い空でないリストを探して そのリストの先頭のプロセスを取り出す スケジューリング方針はそれぞれのプロセスが同じ静的優先度を持つプロセスのリストの中のどこに挿入され このリストの中をどのように移動するかを決定する
最後に