マルチスレッドで実行する 初歩編

 パソコンに大変な処理をさせていると長い処理時間が発生して待っていなくちゃいけなくなります。ところが人というものは勝手な生き物でして、20秒以上変化がないものを、じっと見ていることが出来ません。下手をすると壊れていると思ってリセットを押したりします。
 そこでプログラマーは工夫をして、プロセスバーなんかを表示して「あと何パーセントですよ。」なんて表示して、気を紛らさせる必要が出てきます。
 そういった複数の処理を平行して実行させる処理をマルチスレッドと言います。Iron Pythonを使ってマルチスレッド処理を行う方法を見ていきましょう。

 Threadクラスを使ってみる

 スクリプトを書いていてわかると思うのですが、パソコンは一つ一つの仕事を順番にこなしています。この順番に行っている仕事を一つの道筋の意味から、"スレッド"と言います。
まず例になるシングルスレッドのスクリプトを組んでみましょう。

from System import DateTime

def DoSomething():
  for i in xrange(1000000000):
    pass
  print "終了しました。"
  print DateTime.Now

print "スタートします。"
print DateTime.Now

DoSomething()

print "Enterを押してください"
print DateTime.Now

raw_input()

出力画面:
 スタートします。
 2010/09/19 15:50:32
 終了しました。
 2010/09/19 15:51:01
 Enterを押してください
 2010/09/19 15:51:01
 上記のように、私のパソコンでは100億を数えるのに29秒かかりました。皆さんは待てましたか。では待てないという人のための処理を説明していきましょう。

 マルチスレッドを実施するために、スレッドを扱えるThreadクラスを使います。Threadクラスは、System.Threadingの名前空間で使用できます。その使い方はまずインスタンスを作る際に次のようにします。

 Thread(ThreadStart(実行する関数名))

 ここでThreadStartもSystem.Threadingの名前空間にあるアトリビュートの一つです。
そしてThreadクラスのStartメゾットで関数を実行します。具体的に見ていきましょう。

from System import Datetime ←DateTimeを使うため
from System.Threading import * ←Thread,ThreadStartを使うため


def DoSomething():
  for i in xrange(1000000000):
    pass
  print "終了しました。"
  print DateTime.Now


print "スタートします。"
print DateTime.Now

th = Thread(ThreadStart(DoSomething)) ←インスタンス作成
th.Start() ←スレッドの実行

print "Enterを押してください"
print DateTime.Now

raw_input()

出力画面:
 スタートします。
 2010/09/19 17:07:12
 Enterを押してください
 2010/09/19 17:07:12
 終了しました。
 2010/09/19 17:07:43
 今度は「終了しました」より先に「Enterを押してください」が先に実行されましたね。

 フォアグラウンドとバックグラウンド

 上で作ったスクリプト実行時に「Enterを押してください」と表示されて素直にEnterを押したらどうなるでしょう。スクリプトは終了しません。これは「終了しました」を表示するスレッドがまだ裏で動いているからです。
 「Enterを押してください」を表示させているスレッドのようにユーザーに見える形で処理が進んでいることをフォアグラウンドで実行する。逆に「終了しました」のように裏で動いていることをバックグラウンドで実行すると呼びます。
 しかしWindowsではフォアグラウンド/バックグラウンドは別の意味で使っています。つまりフォアグラウンドは優先順位が高いもの。バックグラウンドは優先順位が低いものして扱い、フォアグラウンドの処理が全て終わった時点でバックグラウンドの処理を全て停止させます。
 今回「終了しました」を表示するスレッドが停止しないのはフォアグラウンドとして扱われていたからと推測がつきます。確かめてみましょう。
 「終了しました」を表示するスレッドがバックグラウンド処理であることを明確にするためにはThreadクラスのIsBackgroundアトリビュートをTrueにします。こんな感じです。

from System import DateTime
from System.Threading import *


def DoSomething():
  for i in xrange(1000000000):
   pass
  print "終了しました。"
  print DateTime.Now

print "スタートします。"
print DateTime.Now

th = Thread(ThreadStart(DoSomething))
th.IsBackground = True ←バックグランドに指定します。
th.Start()

print "Enterを押してください"
print DateTime.Now

raw_input()

 では実際にEnterを押してみてください。いかがでしたか。

 バックグラウンドスレッドの終了を待たせる

 とは言っても、バックグラウンドの処理を強制終了したくない場合もありますね。そういった場合は、『落ち合う』という意味のThreadクラスのJoinメゾットを使います。

from System import DateTime
from System.Threading import *


def DoSomething():
for i in xrange(1000000000):
pass
print "終了しました。"
print DateTime.Now

print "スタートします。"
print DateTime.Now

th = Thread(ThreadStart(DoSomething))
th.IsBackground = True
th.Start()

th.Join() ←ここでthスレッドの終了を待ちます。

print "Enterを押してください"
print DateTime.Now

raw_input()

出力画面:
 スタートします。
 2010/09/19 17:55:19
 終了しました。
 2010/09/19 17:55:51
 Enterを押してください
 2010/09/19 17:55:51
 ちゃんとバックグラウンド処理が終了したのを待ちましたね。
 あれ?この結果って一番最初の結果と同じですね。一周回ってスタート地点って感じがしますが、まあ練習なので仕方ないでしょう。