セマフォ
共有メモリの話題に入る前の準備としてセマフォについて記述する.
二つのプロセス(もしくはスレッド)A,Bが共有する変数xがあったとする.そしてA,B両者がxに1を足すとする.xが0なら処理後のxの状態が2になっていることを期待してみる.
- Aがx(=0)の値を読み込む.
- Aがx(=0)に1を足してそれをxに書き込む.
- Bがx(=1)の値を読み込む.
- Bがx(=1)に1を足してそれをxに書き込む.
- この時x=2.
- Aがx(=0)の値を読み込む.
- Bがx(=0)の値を読み込む.
- Aがx(=0)に1を足してそれをxに書き込む.
- Bがx(=0)に1を足してそれをxに書き込む.
- この時x=1.
上の問題を解決するために,ロック変数yを考えてみる.変数yが0の時は別のプロセスはxに対して処理をせず,1なら処理をする.したがってA,Bはxに対して処理する前にyをチェック,変更する.先のタイミングが悪かったパターンは以下のようになる.
- Aはy(=1)を読み込む.
- Aはyが1なのでこれを0にする.
- Aがx(=0)の値を読み込む.
- Bはy(=0)を読み込む.
- Bはyが0なので待つ.
- Aがx(=0)に1を足してそれをxに書き込む.
- Aはyを1に戻す.
- Bはy(=1)を読み込む.
- Bはyが1なのでこれを0にする.
- Bがx(=1)の値を読み込む.
- Bがx(=1)に1を足してそれをxに書き込む.
- Bはyを1に戻す.
- この時x=2.
- 0か正の整数をとる.
- waitとsignalという二つの操作だけが可能.
- waitをしたとき,値が1より大きければ値をデクリメントする.0ならば待つ.
- signalをしたとき,値が0より大きくなるのを待っているプロセスがあれば,そのプロセスを再開する.なければ値をインクリメントする.
- バイナリセマフォは0と1の二値をとる.
- 汎用セマフォは0と複数の正の値をとる(例えば0,1,2).
- セマフォにwait操作をする.
- クリティカルセクションの実行.
- セマフォにsignal操作をする.
セマフォを使用するための関数には以下の3つがある.
これは親プロセスと子プロセスがそれぞれpまたはcと表示して1秒寝てまたpまたはcと表示するものである.そして親子で同期をとることによって,"pp "と"cc "がそれぞれ一まとまりとして表示されることを期待している.
- int semget(key_t key, int nsems, int semflg)
- keyで指定したnsems個からなるセマフォ集合の識別子を取得する.semflgはオプションでパーミッションなどもここで設定する.semflgでIPC_EXCLを指定するとセマフォが一意となることが保証される(keyがセマフォのために既に使用されていればsemgetは失敗する).
- int semop(int semid, struct sembuf *sops, unsigned nsops);
- semidで指定したセマフォ集合の操作のメンバを操作する.sopsはセマフォに設定したい値を持つsembuf構造体のnsops個の配列である.
- int semctl(int semid, int semnum, int cmd, ...)
- semidで指定したセマフォ集合またはそれのsemnum番目のメンバの制御操作を行なう.操作はcmdで指定する.
- semgetでセマフォを取得する.
- semopでロック.
- semopでアンロック.
- semctlでセマフォを削除.
gcc semaphore.cとするとセマフォではなくただの変数をロックに使用している(ソース中「セマフォ気分」と書いてあるところ).以下実行結果.
pcc p cpc cp pc p cpc p cpc p cpc p cpc p cpc cp pc p cpp cと同期が取れていない.
gcc -DSYNC semaphore.cとするとセマフォを使用して同期を取る.以下実行結果.
pp pp cc cc pp cc pp pp pp cc cc cc pp pp cc cc pp cc pp ccセマフォの効果がはっきりと分かって満足.
で,共有メモリの話でまずセマフォを出したのは,冒頭の話でいうところのxが,共有メモリ上のデータだからである(セマフォのサンプルでは簡単に標準出力への同期だった).そして複数のプロセスで共有するのだから,冒頭の話は当然問題として上がる.そしてその問題を解消する一つの方法がセマフォなのだ.

