「6さいからのプログラミング」第13話は、同時に複数の処理を実行する「マルチスレッド」と、効率良くエラーを処理する「例外」について解説します。1マルチスレッド「マルチスレッド」とは、プログラム中の複数の処理を同時に実行することです。処理を順番に実行していく流れのことを「スレッド」といい、今まで解説してきたような同時に1つだけ処理が実行される場合は「シングルスレッド」と呼びます(図1-1)。シングルスレッドとマルチスレッドマルチスレッドの利点はいくつかあります。「ファイルをダウンロードする」というプログラムを書くと、ダウンロードに10分かかった場合は10分間操作ができなくなりますが、「ファイルをダウンロードする」と「画面にダウンロード待ちのアニメーションを表示する」のようなプログラムをマルチスレッドで実行させると、ダウンロードしながら別の処理を同時に行うことができます。多くのCPUには同時に複数の処理を行う能力があり、マルチスレッドを活用すると計算を並列化させて高速化させることもできます。1.1ロックさて、そのような便利なマルチスレッドですが、スレッドが複数あるとそれぞれの処理のタイミングが予測できないため、例えば図1-2のように、同じ変数にアクセスしている場合などに予期しない動作をすることがあります。同じ変数にアクセスするこの図では、スレッド1では「aが0である」という前提で処理したいにもかかわらず、スレッド2の処理のタイミングによってif文の中でaが1になってしまうことがあります。このような問題を解決するために、マルチスレッドには「ロック」という仕組みが用意されています。「ロック」とは、あるスレッドがロック状態になっているときに他のスレッドがロックしようとすると、後からロックしようとしたスレッドはロック状態が解除されるまで待ち続けるという機能です(図1-3)。ロックこの図では、スレッド2がロックしようとしたときにスレッド1が既にロック状態だった場合、スレッド1のロックが解除されるまでスレッド2は待ち続けるので、if文内でaが1になることはなく、画面には必ず「0」が表示されます。このようなロックの方法は、一般的に「ミューテックス(mutex)」と呼ばれます。このほかにも同時にロックできるスレッドの数を複数指定できる「セマフォ(semaphore)」など、いろいろなロックの方法があります。1.2プログラム上で書くにはプログラムで実際にマルチスレッド処理を書くには、C言語や古いC++の規格には標準でこの仕組みが用意されていないため、OSが提供する機能を利用して実現することになります。C++を用いて先ほどのようなプログラムを書く場合、Windows.hに含まれる関数を用いて図1-4のように書きます。説明なしには細部がよく解らないと思いますが、全体の流れや雰囲気を掴んでください。multithreading.c7~27行目で「Thread1」と「Thread2」という関数を作成し、Windows.hの「CreateThread」という関数を使って33~34行目でこれらをマルチスレッドで実行しています。CreateThread関数を実行した瞬間、Thread1やThread2はメインのスレッドとは別のスレッドとして開始されます。ロックを行うには、5行目の「CRITICAL_SECTION」型の変数csを、33行目のように「InitializeCreateSection」関数で初期化しておき、10行目や23行目の「EnterCriticalSection」関数でロックし、その後「LeaveCriticalSection」関数でロック解除する流れです。C#やJavaには標準でロックの仕組みが用意されています。C#で先ほどのプログラムを書く場合は、図1-5のようになります。multithreading.csこのようにC#では「lock」という構文を使ってロックします。以上が「マルチスレッド」の説明になります。2例外ここからは「例外」について解説します。「例外れいがい」とはその名の通り、本来行われるべきではない例外的な処理、つまりエラーを扱う仕組みです。C言語にはありませんが、C++、C#、Javaなど多くの言語に搭載されています。例外を使わずにエラーを扱う場合は、関数の戻り値でエラーコードを返すことがよく行われます(図2-1)。関数の戻り値でエラー処理をするこの例では、処理に成功した場合は0を、失敗した場合は1を返すことにより、関数の呼び出し元でエラーが発生したかどうかを知ることができます。エラー以外に返したい戻り値がある関数では、戻り値が既に使われているために工夫をする必要があります。図2-2のように、関数の中で関数が呼び出される場合にはエラー処理を何重にも書く必要があり煩雑になります。関数の中で関数を呼ぶ2.1例外のthrowとcatchそこで、関数を縦断してエラー情報を受け渡せる仕組みが作られました。エラー情報を含めたインスタンスのことを「例外」と呼び、例外を発生させることを「throwスロー」といいます。throwされた例外を「catchキャッチ」してエラー処理を行います(図2-3)。例外のthrowとcatch関数G内で例外がthrowされると、catchされるまで関数を次々と抜けていきます。この図では例外はmain関数でのみcatchしているため、関数Fや関数Gで発生したエラーはすべてmain関数で処理することになります。「どこで例外が発生したか」は無関係に「どのような例外が発生したか」で処理できるため、様々な関数で発生するエラーをまとめて扱うことができます。2.2finallyこのように、例外がthrowされると関数を抜けていきますが、ヒープから確保した領域を解放したいなど、関数を抜ける前に必ず行っておきたい後処理があります。それを実現するのが「finallyファイナリー」です。finallyを書くと、例外がthrowされたときにはfinally内の処理を行ってから関数を抜け、外側の関数のcatch処理に任せます。例外がthrowされなかった場合でも、finally内の処理を行ってから次の処理へと進みます。2.3プログラム上で書くにはプログラム上で例外処理を書く場合、例外のthrowには「throw文」を使い、例外のcatchやfinallyには「try-catch-finally文」を使います。どの範囲で発生する例外に対し処理するかを「try」で囲んで示し、必要に応じてcatchやfinallyを記述します。C++では図2-4のように書きます。exception_handling.cppこの場合、関数Gの4行目でthrowした「1」という例外は、main関数の18行目でcatchされ、変数eには「1」が入ります。仮に関数G内で例外をthrowしなかった場合は、main関数の「catch{}」内に書いた処理はスキップされて実行されません。ちなみにC++にはfinallyは無く、finallyに相当する処理は別の方法で実現します。C#では図2-5のように書きます。exception_handling.csこの場合、関数Gの6行目で例外がthrowされると、関数Fの15行目の「finally{}」の処理が行われてから、Main関数の27行目でcatchされます。今回は「マルチスレッド」と「例外」について説明しました。次回は、基本編の最終回。世の中の実際のプログラムがどのように作られているのかを紹介します。世の中の実際のプログラム前へ目次ホームへ次へ©Kuina-chan1579924034jaf。