はなおかじった [著] 2010/04/26 14:00

本記事は現在、新規コメントの受付を中止しております。ご了承ください。また、執筆者からの依頼により編集部判断で2010年5月2日以降のコメントを削除いたしました。

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

1 2 3 →

はじめに

 この記事は、インドリ氏による『スレッドセーフとインテル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値
999int push(char v) { 999
999if (index < ARRAY_MAX) { 999
999array[index] = v;int push(char v) {999
999++index;if (index < ARRAY_MAX) {1000
1000return 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)並列プログラミングに限った問題ではない

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


1 2 3
→
INDEX
マルチスレッドを安全に実行する
Page1
はじめに
「競合」という問題
逐次処理で発生する並列化と同じ問題
「競合問題」の解決
「1行」なら安全か
まとめ
参考資料
プロフィール
はなおかじった ハナオカジッタ

わんくま同盟で、ブログを書いています。

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


記事へのコメント・トラックバック機能は2011年6月に廃止させていただきました。記事に対する反響はTwitterやFacebook、ソーシャルブックマークサービスのコメントなどでぜひお寄せください。

スポンサーサイト