Think Parallelで行こう!

第4回 並列処理プログラミングの基本用語

株式会社フィックスターズ
好田 剛介

2009/12/18

基本的な用語を覚えよう―ミューテックス

 先ほどのプログラムでは、マルチスレッドによる並列実行ができたものの、出力文字列が混ざってしまいました。

 それでは困る場合もありますので、複数のスレッド間で資源をうまく共有するための仕組みが必要となります。

 Boost.Threadにはそのような目的のための機構としてミューテックス(Mutex:mutual exclusion:相互排他)が用意されています。

 Boost.Threadのミューテックスにはさまざまな使い方があり、lock関数やunlock関数を使うもの、boost::mutex::scoped_lockクラスをローカル変数(局所変数)として生成してから破棄(その中かっこを出る)までロックするものなどがあります。

 ミューテックスでの排他制御をすべき代表的な資源として、複数のスレッドから使用されるグローバル変数(大域変数)があります。この場合、グローバル変数1つあるいは関連するグローバル変数をまとめたグループに対して、1つのミューテックスを用意します。

 各スレッドはグローバル変数をアクセスする場合、以下のようなルールに従ってグローバル変数へのアクセスを行います。

  1. ミューテックスをロックする
  2. グローバル変数の読み書きをする
  3. ミューテックスをアンロックする

 もし、ほかのスレッドがこのグローバル変数にアクセスしていない場合、ミューテックスのロックはすぐに終了し、グローバル変数へのアクセスを行うことができます。

 逆にほかのスレッドがこのグローバル変数にアクセスしている場合、ほかのスレッドがアンロックするまで待たされた後にロックが取得でき、グローバル変数にアクセスできます。

 このような仕組みを使って、排他的なアクセスを実現できます。

 次のコードは標準出力を共有資源と考えて排他制御を行った例です。ミューテックスによる排他制御を行わない場合には、各スレッドの出力する文字列が混ざってしまうことがあるのに対して、ミューテックスによる排他制御を行う場合にはそのようなことは起こりません。

●sample_boost_mutex.cxx
#include <iostream>
#include <string>
#include <boost/thread.hpp>

class ParaFunc
{
private:
    boost::mutex & m_mtx;
    const std::string m_str;

public:
    ParaFunc(boost::mutex & m, const std::string & s)
                : m_mtx(m), m_str(s) {}
    void operator () (void)
    {
        boost::mutex::scoped_lock lock(m_mtx);
        for (int i = 0; i < 3; ++i)
        {
            std::cout << m_str << std::endl;
        }
    }
};

int main(void)
{
    boost::mutex mtx;

    ParaFunc pf0(mtx, "0");
    ParaFunc pf1(mtx, "1");
    ParaFunc pf2(mtx, "2");

    boost::thread th0(pf0);
    boost::thread th1(pf1);
    boost::thread th2(pf2);

    th0.join();
    th1.join();
    th2.join();

    return 0;
}

 sample_boost_mutex.cxxとして保存し、コンパイルします。

$ g++ sample_boost_mutex.cxx -lboost_thread -o sample_boost_mutex.elf

 実行してみましょう。

$ ./sample_boost_mutex.elf
0
0
0
2
2
2
1
1
1

 今回の結果は文字が混ざらず、排他制御が行われていることが分かると思います。

 では、このプログラムの解説です。

 main関数の先頭26行目、排他制御に必要となるboost::mutexクラスの変数mtxを定義します。この変数を使って各スレッドは共有資源の排他制御を行います。

 次に28行目でスレッド化されるParaFuncクラスの変数を定義します。この時、各変数に対して先程定義したmtxを渡しておきます。

 その後、main関数でboost::threadクラスの変数が定義されスレッドが生成される部分から先は同じですが、先程のpara_func関数に相当するのが、14行目のParaFuncクラスでvoid operator () (void)と定義されている関数です。boost::threadクラスの変数によりそれぞれ別スレッドとして実行が開始されます。

 operetor ()関数の先頭ではboost::mutex::scoped_lockクラスの変数によってミューテックスのロックが取得され、続くforループで標準出力へ出力を行った後、operator ()関数がリターンするのに合わせて自動的にミューテックスがアンロックされます。

そのほかの基本的な用語

 スレッドを用いた並列プログラムを書く場合、このほかにも従来のプログラムでは見慣れない用語が必要となります。それら基礎的な用語について説明します。

同期(syncronization)

 複数のスレッドが動作する場合、各スレッドの作業が終わるのを待ってからでないと次の作業が始められない場合があります。このようなスレッドの待ち合わせを同期と呼びます。

レースコンディション(race conditions)

 各スレッドは実行するコンピュータやオペレーティングシステムなどの外部要因によっては、一切邪魔されることなく処理を実行できたり、とぎれとぎれに処理を実行できたりします。すると、前回はうまく実行できたプログラムが、今回は間違った結果を出力してしまうというように、実行ごとに結果が変わってしまう場合があります。

 このような実行ごとに結果が変わることをレースコンディションと呼びます。このようなことが起こらないように、資源の排他制御やスレッド間の同期を適切に行わなければなりません。

デッドロック(deadlocks)

 同期や排他制御が適切に行われていない場合、各スレッドがそれぞれ別のスレッドを待ってしまい、結果的にすべてのスレッドが待ち状態のまま先に進めなくなってしまう場合があります。このような状況をデッドロックと呼びます。

並行(concurrent)

 並列でも疑似並列でもいいが任意の順番で実行したい場合があります。このような概念を並行と呼びます。

 次回は、並列プログラムの設計からスレッドによる並列化プログラミングの実際を解説します。


prev
2/2

Index
並列処理プログラミングの基本用語
  Page1
「並列化されていないコードよりは速く」から始めよう
Boost C++ライブラリ

基本的な用語を覚えよう―スレッド
Page2
基本的な用語を覚えよう―ミューテックス
そのほかの基本的な用語
index Think Parallelで行こう!


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間