まっつーのブログ

本の感想や振り返りなど雑多に書いてます

第12章 並行プログラミング

12章について
この章では、並行プログラミングを書くための基本的なメカニズムについて説明されていました。
プロセス、スレッド、マルチプレクシングと異なる手法を用いた実装方法をはじめ、それぞれの利点と欠点についても学ぶことができます。
以下まとめです。


プロセス

サーバが接続要求を受け取ったら子プロセスに処理を引き継がせることで、自身は別の接続要求を受け付けられる。

利点 : ユーザのアドレス空間が共有されないため、誤って別プロセスの仮想メモリを上書きする恐れがない。

欠点 : プロセスが状態情報を共有することが難しい。動作が遅い。


I/Oマルチプレクシング

エコー・サーバを作る場合、接続要求とキーボードからの入力の2つの I/O イベントに応答する必要がある。
しかし片方のイベントを待っているともう片方を待つことができない。 そのため、I/Oマルチプレクシングを利用することでプロセスを一時停止状態にし、準備ができたものから順に処理していく。


利点

  • フロー間でデータ共有やデバッグがしやすい。
  • イベント駆動設計はプロセス・ベースの設計よりも効率が良い。

欠点

  • コーディングが複雑になる。
  • マルチコア・プロセッサを十分に活用できない。


スレッド

スレッドはプロセスのコンテクストで走る論理フローである。
また最新のシステムでは、単一のプロセス内で並行して複数のスレッドを走らせることができる。

利点

具体的にいうと
グローバル変数とローカル静的変数(関数内で宣言されたstatic変数)は、スレッド間で読み書き可能だが、 static をつけずに関数の内側で宣言された変数は共有されない。


スレッドに基づく並行サーバ

全体の構造はプロセス・ベースの設計に似ている。
メイン・スレッドは繰り返し接続要求を待って、要求を処理するためのピア・スレッドを作成するから。


セマフォによるスレッドの同期

システムがスレッドのために正しい順番を選択してくれるかどうかを予測できない。

セマフォ

非負整数を持ったグローバル変数で、P および V とよばれる操作によってのみ変更される。

P(s) : s を 1 減算して処理に戻る。s が 0 なら 1 以上になるまで待ち状態にする。
V(s) : s を1 加算する。待ち状態のスレッドは、V 操作をすることで処理を再開させる。

// s の初期値 = 1
for (int i = 0; i < n; i++) {
    P(&s); // sが減算されて0になるので他のスレッドは待ち状態になる
    cnt += 1;
    V(&s); // sが加算されて他の1スレッドがcntにアクセスできる
}

バイナリ・セマフォ (ミューテックス) : 値が常に 0 か 1 のセマフォのこと。
計数セマフォ : 利用可能な資源の集合のカウンタとして使われる。

注意点
同期操作(P と V)は単一のメモリ更新に比べて非常にコストがかかる。そのため、可能であれば避けるようにする。
スレッドごとにローカル変数で前計算をした結果を最終的に同期操作を使って書き込むなどがよさそう。


事前スレッド化

新しいクライアントごとにスレッドを作成するとコストがかかるので事前にいくつかのスレッドを用意する。
メイン・スレッドといくつかのワーカー・スレッドで構成する。

// mainで事前にワーカー・スレッドを生成する
for (i = 0; i < 10; i++) {
    pthread_create(&tid, NULL, thread, NULL); // 処理が来るまで待たせる
}


平行プログラムの性能

例 : 4 コアシステム上で、n = 231 個の要素の数列の合計を求める場合

実行時間は 4 スレッドまでは減少するが、そこからはむしろ少し増加し始める。
1 つのコアで 1 スレッド動かしているときが最大のパフォーマンスを発揮する理由は、複数のスレッドのコンテクスト・スイッチのオーバーヘッドによるもの。


並行性の問題

スレッド・セーフ

スレッド・セーフな関数を使用する。
スレッド・アンセーフな関数を使う場合はミューテックスで保護する。


リエントラント性

リエントラントな関数は、複数のスレッドによって呼ばれるときに、いかなる共有データも参照しない。
同期操作が必要ないため効率が良い。

またスレッド・アンセーフな関数をリエントラントになるように書き換えればスレッド・セーフとして扱える。


暗黙的リエントラント

リエントラント関数が参照渡しを許可した場合、スレッドが非共有データへのポインタを渡したときだけリエントラントとなる。


デッドロック

ある一組のスレッドが一連のリソースの獲得で競合したまま、永久にブロックされた状態に陥っている状態のこと。
スレッドが同じ順番でミューテックスを獲得するならばデッドロックは発生しない。


感想

これまでスレッドを増やせば単純に速度が向上するのかと思っていましたが、制御によるオーバーヘッドなども考慮しないといけないみたいですね。
またリエントラントについても、これまで意識していなかったので、今後並行プログラミングを扱うときに意識してみようと思いました。

そして、約4ヶ月かかりましたが無事最終章まで読み切ることができました。
実際には、図やコードサンプル、練習問題など深く学べるのでコンピュータ・システム全体を学びたいという方にオススメです。
かなり厚みもあるので、自分はこれから枕にして寝ようと思います。
以上です。お読みいただきありがとうございました。