SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

C++0xのマルチスレッド機能

  • X ポスト
  • このエントリーをはてなブックマークに追加

データの保護

 C++0xのスレッドライブラリでは、多くのスレッド処理APIと同様、共有データを保護するための基本機能としてミューテックスを使用します。C++0xのミューテックスには次の4種類があります。

  • 非再帰的ミューテックス(std::mutex
  • 再帰的ミューテックス(std::recursive_mutex
  • ロック関数でのタイムアウトが可能な非再帰的ミューテックス(std::timed_mutex
  • ロック関数でのタイムアウトが可能な再帰的ミューテックス(std::recursive_timed_mutex

 どの種類のミューテックスでも、1つのスレッドを排他的に所有できます。非再帰的ミューテックスの場合は、同じスレッドから2回続けて(途中で解放せずに)ロックしたときの動作は未定義です。一方、再帰的ミューテックスの場合は、ロックカウントが増えるだけです。その場合、ロックしたのと同じ回数だけ解放しないと、他のスレッドがそのミューテックスをロックできません。

 4種類のミューテックスには、ロックと解放を行うためのメンバ関数がそれぞれ用意されています。しかし大抵の場合は、std::unique_lock<>std::lock_guard<>というロッククラステンプレートを使うことをお勧めします。これらのクラスは、コンストラクタでミューテックスをロックし、デストラクタで解放するようになっています。これらをローカル変数として使用すれば、スコープを抜けるときにミューテックスのロックは自動的に解放されます。

std::mutex m;
my_class data;

void foo()
{
    std::lock_guard<std::mutex> lk(m);
    process(data);
}   // mutex unlocked here

 std::lock_guardはあえて基本機能のみとなっており、上記のような使い方だけが可能です。一方、std::unique_lockには、遅延ロック(deferred locking)、ロックの試行、タイムアウト付きのロックの試行、オブジェクトの破棄前のロック解放などの機能があります。ロックのタイムアウト機能が目的でstd::timed_mutexを利用する場合は、大抵はstd::unique_lockを使うことになります。

std::timed_mutex m;
my_class data;

void foo()
{
    std::unique_lock<std::timed_mutex>
        lk(m,std::chrono::milliseconds(3)); // wait up to 3ms
    if(lk) // if we got the lock, access the data
        process(data);
}   // mutex unlocked here

 これら2つのロッククラスはテンプレートなので、標準のミューテックス型すべてに対して利用できるほか、lock()関数とunlock()関数を持つ他の型にも利用できます。

複数のミューテックスをロックするときのデッドロックを防ぐ機能

 場合によっては、複数のミューテックスをロックする処理が必要になることがあります。このとき一歩間違うと、忌まわしいデッドロックが発生しかねません。デッドロックとは、2つのスレッドが、2つの同じミューテックスを互いに逆の順序でロックしようとしたために、それぞれ1つをロックした状態で相手の終了を待ち続けるという状況に陥ることです。

 C++0xのスレッドライブラリにはこの問題への対策として、複数のロックを一度に要求したい場合のために、複数のミューテックスをまとめてロックできるstd::lockというジェネリック関数が用意されています。各ミューテックスに対してメンバ関数lock()を順番に呼び出すのではなく、全部まとめてstd::lock()に渡すことで、デッドロックを心配せずにすべてをロックできるという機能です。この関数には、ロック前のstd::unique_lock<>のインスタンスを渡すこともできます。

struct X
{
    std::mutex m;
    int a;
    std::string b;
};

void foo(X& a,X& b)
{
    std::unique_lock<std::mutex> lock_a(a.m,std::defer_lock);
    std::unique_lock<std::mutex> lock_b(b.m,std::defer_lock);
    std::lock(lock_a,lock_b);

    // do something with the internals of a and b
}

 上の例で、仮にstd::lockを使わなかった場合、デッドロックが生じる可能性があります。例えば、X型の2つのオブジェクトxyに対し、一方のスレッドがfoo(x,y)、もう一方がfoo(y,x)を呼び出した場合です。std::lockを使っていれば、その心配はありません。

初期化時のデータの保護

 データを初期化する際にのみ保護が必要な場合には、ミューテックスではうまくいきません。初期化の完了後も無駄な同期が行われてしまうからです。C++0xの標準には、これに対処する方法がいくつか用意されています。

 1つ目は、コンストラクタをC++0xの新しいconstexprキーワードを使って宣言し、さらにこのコンストラクタで定数の初期化の要件を満たすことです。この場合、コンストラクタで初期化される静的ストレージ期間のオブジェクトは、コードの実行に進む前に、静的初期化フェーズの一環として確実に初期化されることが保証されます。これはstd::mutexで利用する方法です。これにより、グローバルスコープでのミューテックスの初期化と競合する可能性を回避できます。

class my_class
    {
        int i;

    public:
        constexpr my_class():i(0){}

        my_class(int i_):i(i_){}

        void do_stuff();
    };

    my_class x; // static initialization with constexpr constructor

    int foo();
    my_class y(42+foo()); // dynamic initialization

    void f()
    {
        y.do_stuff(); // is y initialized?
    }

 2つ目は、ブロックスコープで静的変数を使用することです。C++0xでは、ブロックスコープの静的変数の初期化は、関数の最初の呼び出し時に行われます。初期化の完了前に別のスレッドが同じ関数を呼び出した場合、2番目のスレッドは待機しなければなりません。

void bar()
{
    static my_class z(42+foo()); // initialization is thread-safe

    z.do_stuff();
}

 どちらの方法も使えない場合(オブジェクトを動的に生成する場合など)は、std::call_oncestd::once_flagを使うのが最適です。std::call_oncestd::once_flag型の特定のインスタンスと組み合わせて使用すると、call_onceの名が示すとおり、指定した関数は1回のみ呼び出されます。

my_class* p=0;
std::once_flag p_flag;

void create_instance()
{
    p=new my_class(42+foo());
}

void baz()
{
    std::call_once(p_flag,create_instance);
    p->do_stuff(); 
}

 std::threadのコンストラクタと同様に、std::call_onceは関数の代わりに関数オブジェクトを受け取ることができ、複数の引数を取ることもできます。引数はデフォルトではコピーされ、std::refでラップすると参照渡しになるという点も同じです。

次のページ
イベントの待機

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Anthony Williams(Anthony Williams)

Just Software Solutions Ltd.のテクニカルディレクター。顧客向けのカスタムソフトウェア開発に主に従事。WindowsおよびC++による開発が中心。Boost Threadライブラリのメンテナーで、BSI C++ Standards Panelのメンバーでもある。近刊の『C+...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/3287 2008/12/01 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング