vieweditattachhistorytopchangessearchhelp

第30回


■SqueakではじめるSmalltalk入門   第30回


 本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。

 ブロックは、手続きをオブジェクトとして扱うためのものとして存在し、処理結果ではなく手続きごと受け渡しを可能とすること。また、Smalltalkではその特性を利用し、ブロックをメソッド(関数)のパラメータ(引数)に添えることで様々な制御構造を実現していること、について書きました。今回はブロックの扱いの最後として、Smalltakにおける並列処理について簡単に触れます。

 まず、次のようなスクリプトを考えてみましょう。行頭にはコメントのかたちで行番号を振ってあります。

"01"   | array size |
"02"   size _ 10.
"03"   array _ Array new: size.
"04"   1 to: size do: [: idx |
"05"      array at: idx put: #a].
"06"   ^ array


 このスクリプトは、sizeに束縛したサイズの配列を作り、その要素を#aで埋めます。print it (cmd+P) で評価し、結果を得ることが可能です。

#(#a #a #a #a #a #a #a #a #a #a)


 三行目から四行目、1に対して送信されるto: size do: [...]というメッセージ式は、前回紹介したように、1からsizeまでを繰り返す、いわゆるfor-nextループ処理をシミュレートしています。#at:put:は、配列の指定した要素に指定したオブジェクトを束縛するためのメソッドです。

 この式の前後を[ ]で括るとブロックにできることと、ブロックは、value(ブロック変数のあるときは、value: arg、value: arg1 value: arg2、…)というメッセージを受けることで、自身に記述された手続きを評価して結果を返すことはすでに何度かご説明したとおりです。そこで、次のように書いても上のスクリプトと同じことになります。

| array size |
size _ 10.
array _ Array new: size.
[1 to: size do: [: idx |            "<= 行頭に[を追加"
   array at: idx put: #a]] value.   "<= 行末に] valueを追加"
^ array   " => #(#a #a #a #a #a #a #a #a #a #a) "


 このvalueの代わりに「fork」を送信することで、ブロックに記述した手続きを別のスレッドにフォークする、つまりプロセスを“枝分かれ”させることが可能です。拍子抜けするほど簡単ですね。

| array size |
size _ 10.
array _ Array new: size.
[1 to: size do: [: idx |
   array at: idx put: #a]] fork.   "<= valueをforkに変更"
^ array   " =>  #(nil nil nil nil nil nil nil nil nil nil) "


 要素を#aで埋める作業は、別スレッドで行なわれるので、print it (cmd+P)の結果では、まだ、配列は作られた直後の状態、つまりすべての要素はnil(未定義値)のままです。余談ですが、print itの代わりにinpect it (cmd+I)すると、不思議なことに今度は中身が埋まった状態になります。これは、インスペクタを起動している間にフォークした処理が終わるからです。

 これはこれでおもしろいのですが、何かと不便なので、フォークした処理が終わるまで待ってから結果を表示させるようにしましょう。

| array size |
size _ 10.
array _ Array new: size.
[1 to: size do: [: idx |
   array at: idx put: #a]] forkAndWait.  "<= forkをforkAndWaitに"
^ array   " =>  #(#a #a #a #a #a #a #a #a #a #a) "


 #forkAndWaitは、フォークする処理が終わるまで待つセマフォ(a Semaphore。腕木信号機)を内部的に介在させ、自動的に同期をとってくれるメソッドです。興味があるかたは、forAndWaitを選択しbrowse it (cmd+B)で定義を見てください。

 では、このスクリプトに、今度は#aではなく、#Aで要素を埋める手続きを追加してみましょう。もちろんブロックとして記述し、プロセスはフォークさせます。

| array size |
size _ 10.
array _ Array new: size.
[1 to: size do: [: idx |           "<= 追加"
   array at: idx put: #A]] fork.   "<= 追加"
[1 to: size do: [: idx |
   array at: idx put: #a]] forkAndWait.
^ array   " =>  #(#a #a #a #a #a #a #a #a #a #a) "


 しかし結果は追加以前と変わりません。Squeakシステムの並列処理は優先順位が同じなら、フォークされた順に待ち合わせ、逐次実行されるからです。順番待ちしている別のスレッドに実行のチャンスを与えるには、Processor yieldというメッセージ式を処理の途中に挿入しておく必要があります。

| array size |
size _ 10.
array _ Array new: size.
[1 to: size do: [: idx |
   array at: idx put: #A.
    Processor yield]] fork.
[1 to: size do: [: idx |
   array at: idx put: #a.
   Processor yield]] forkAndWait.
^ array   " => #(#a #A #a #A #a #A #a #A #a #A) "


 Processorはa ProcessSchedulerを束縛するグローバル変数で、プロセスの監視役です。yieldを受信すると、実行中のプロセスをいったん中断し、待ちプロセスに機会を与えます。これで仲良くスレッド間で譲り合って要素を埋めてゆくので、結果は#A、#aの交互になります。

 valueとほぼ同じ感覚で利用できるforkですが、評価結果を返すvalueと違い、forkはフォークした処理本体(a Process)を返します。これをグローバル変数などに束縛しておけば、任意のタイミングで、suspendやresumeを送信して一時停止や再開、あるいは、terminateを送信することで失効させることが可能です。次のスクリプトは画面左上に現在時刻を表示します。

Smalltalk at: #TIMER put: nil   "グローバル変数TIMERの定義"

TIMER _ [
    [Time now print24 displayAt: 10 @ 10.
    (Delay forSeconds: 0.1) wait] repeat] fork


 ここでフォークされるプロセスは、repeatを使った永久ループになっているので、先の例のように自動的には停止しません。止めるには、次のメッセージ式を評価します。

TIMER terminate

このページを編集 (4988 bytes)


Congratulations! 以下の 1 ページから参照されています。

This page has been visited 994 times.