CodeZine(コードジン)

特集ページ一覧

マルチスレッドを安全に実行する

スレッドセーフの勘所

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2010/04/26 14:00

 この記事では、マルチスレッドについて、過去に掲載された誤解を招く表現を多く含む記事を訂正し、マルチスレッドプログラミングを安全に設計する方法を説明します。

目次

はじめに

 この記事は、インドリ氏による『スレッドセーフとインテルTBBのコンテナ』に記載されている誤りを訂正することを目的としています。インドリ氏の記事では、TBBコンテナの紹介に注意するあまり、マルチスレッドプログラミングに潜む危険、その危険を取り除く方法についての記述が正しくありません。本記事では、マルチスレッドプログラミングを安全に設計する方法を説明することを目的とします。

 本記事で用いるコードは、C言語に類似していますが、C言語ではありません。振る舞いを理解していただきやすくするための仮想言語です。実行できる環境はありません。

「競合」という問題

 ここに、1つのリンゴがあります。そして、2人の人が、そのリンゴの前にいます。ここで2人に向かって「リンゴを食べて良いですよ」とだけ言うと、どうなるでしょうか。お互いに譲り合うか、もしくは取り合いをするでしょう。ここで2人が仲良くリンゴにありつくためには、調停者がいて、どのように分けるかを決めることが必要です。もちろん、2人のうちどちらかが調停者を兼ねてもかまいません。

 並列プログラミングを行うということは、このように、2人以上の人に何らかの作業を依頼することにたとえられます。これらの作業を依頼された人がそれぞれ別個の作業を行うなら、何の問題もありません。しかし、複数の人が同じ資源を操作するようなことがあると、問題が発生します。

 List1は、1000個の文字を格納できるスタックです。

List1.マルチスレッドで問題がある仮想コード
#define ARRAY_MAX 1000
static char array[ARRAY_MAX];
static int index = 0;

int push(char v) {
    if (index < ARRAY_MAX) {
        array[index] = v;
        ++index;
    }
    return index;
}

char pop(void) {
    if (index > 0) {
        return array[index];
        --index;
    }
    return NULL;
}

int howmany(void) {
    return index;
}

 このコードは、シングルスレッドで実行している限り、安全に実行できます。しかし、マルチスレッドでは、安全ではありません。マルチスレッドでは、コードが同時に実行されます。次のように実行がなされたとき、どのようになるでしょうか。なお、これらの実行直前で、indexは999、つまりあと1つなら追加できる状態で、2つ追加しようとしている状況です。

表1
実行前の スレッド1 スレッド2 実行後の
index値 実行行 実行行 index値
999 int push(char v) {   999
999 if (index < ARRAY_MAX) {   999
999 array[index] = v; int push(char v) { 999
999 ++index; if (index < ARRAY_MAX) { 1000
1000 return index; array[index] = v; 1000
1000   ++index; 1001
1001   return index; 1001

 スレッド2のif文でindex値を参照した後、スレッド1のインクリメントが実行され、indexの値が1000となりました。するとスレッド2では、array配列の1001番目の要素にアクセスすることになります。これを実行した結果どうなるかは、実行環境により異なりますが、アプリケーションのどこかで、期待しないエラーが出る可能性が高くなります。

 さて、このコードの問題は何でしょうか。それは、index変数の1つのインスタンスが複数のスレッドで共有されることです。index変数がどのようなアクセススコープを持っていても、関係はありません。複数のスレッドが同じインスタンスを共有すると、問題が発生します。

逐次処理で発生する並列化と同じ問題

(「並列処理とコンテナ:スレッドセーフとは何か」より)

並列プログラミングを行う際には、従来の逐次プログラミングでは考えなかったことを考えなくてはなりません。そのうちの1つが、並列処理で同時にコンテナを操作する時に起こる問題についてです。これはよく「マルチスレッドプログラミングではスレッドセーフなコンテナを使う必要がある」と表現されます。

 インドリ氏のこの書き方は、誤解を招く表現を多く含んでいます。

1)「並列プログラミング」に対する誤解を与える

 「並列プログラミングを行う際には」という表記と、「マルチスレッドプログラミングでは」という表記が混在することにより、並列プログラミングをマルチスレッドプログラミングに限定するような表現がされています。しかし、「並列プログラミング」には「マルチスレッド」以外にも「マルチタスク」があります。もっとも、「マルチタスク」の場合は「プログラミング」という小さな単位ではなく、システムという大きな単位になります。

2)従来の逐次プログラミングというほど、並列プログラミングは新しいわけではない

 「並列プログラミング」には、「マルチスレッド」だけでなく「マルチタスク」が含まれます。マルチタスクはMS-DOSからWindowsに移行したとき、すなわちWindows2.0から発生しており、これは1987年に誕生しています。ただし、完全なマルチタスクOSには1993年のWindows NT 3.1まで待たなければなりません。しかし、UNIXについては、生まれたときから完全なマルチタスクOSでした。

3)並列プログラミングに限った問題ではない

 スレッドクリティカルの根本的な問題は、複数の処理が同じインスタンスを操作することです。これは、並列プログラミングに限った問題ではありません。データベースを扱う場合や、非同期実行を行う場合、一時ファイルやセマフォといった共有メモリなどを扱う場合など、逐次プログラミングであっても同様に発生します。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2010/05/12 22:34 2ページ目。「C++標準ではcoutがスレッドセーフと規定されているわけではない」ことを追記。

  • 2010/04/27 21:54 1ページ目のコード中の&amp;を修正。

  • 2010/04/26 22:29 2ページ目コード中の"&"、"<"がおかしくなっていたので修正。

  • 2010/04/22 21:22 2ページ目最後の1行を追記

あなたにオススメ

著者プロフィール

  • はなおかじった(ハナオカジッタ)

    わんくま同盟で、ブログを書いています。 2004年10月から5年間連続で、Microsoft Most Valueable Professional Award for ASP/ASP.NET を受賞させていただきました。コミュニティの皆様のおかげです。ありがとうございます。

バックナンバー

連載:インテルTBBを通じて学ぶ並列処理
All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5