マルチスレッド実験室

戻る



超ミニBASIC for H8/3048のマルチスレッド化に挑戦してみました。

・発端

 超ミニBASICをH8からSHに移植しているときに気付きました。「マルチプロセスやマルチスレッドって意外と簡単じゃないか?」と。
 SHは絶対アドレッシングが弱い(というか、無いに等しい)CPUです。H8のようにBASICインタプリタのワークエリアを絶対アドレッシングでアクセスしていては効率悪くてたまりません。そこで、超ミニBASIC for SHではワークエリアの先頭番地をGBRに入れておき、GBR相対アドレッシングでアクセスしています。

H8

	MOV.B	@SKPFLG,R0L

SH
(絶対アドレッシング的プログラミング)

	MOV.L	A_SKPFLG,R13
	MOV.B	@R13,R0

	:

A_SKPFLG .DATA.L SKPFLG

SH
(GBR相対によるプログラミング)
※GBRにはWRKTOPの値が入っています

	MOV.B	@(SKPFLG-WRKTOP,GBR),R0

 そこでふと気付いたのは、「BASICインタプリタのワークエリアを複数用意しておき、GBRだけ切り替えれば、コンテキストスッチングは簡単なんじゃないか」ということです。

 と、いうわけでマルチスレッド超ミニBASICをH8で作りはじめました。それほど速度が要求されない用途なら組み込み制御などにも使えると思います。

 SHではなくH8なのは、とりあえずH8のほうがアセンブラでのプログラムが楽だからです。H8でうまくいったらSHにも移植します。SHのGBRのかわりにER5をスレッドごとのワークエリアのベースにしました。
 マルチプロセスではなくマルチスレッドなのは、私としては現時点ではマルチスレッドの方に興味があるのと、マルチプロセスは大量のメモリを積まないとキツイのではないかと思われるからです。キャラクタ液晶画面のイメージなどもプロセスごとに持つ必要があります。

・なにができるの?

 マルチスレッドができるとどんなことができるかですが、たとえば7セグメントLED複数桁をダイナミック点灯したいとします。この場合、LEDの桁スキャン専用のスレッドを作ってそれにまかせておけば、メインのプログラムは本来の処理に専念できます。従来、そういうことをするにはマシン語でタイマ割り込みなどを使わなければいけなかったのですが、BASICがマルチスレッドになればオールBASICで簡単にできます。

・スレッドとは

 あまり馴染みの無い言葉でしたが、某巨大掲示板のおかげで最近はあちこちで目につきますね。「スレッド(thread)」の本来の意味は「織物の糸」です。コンピュータ用語としてのスレッドは、OS、言語によって微妙に定義が異なりますが、おおむね同一プログラムの中の、1つの連なった処理の流れを意味するようです。

・ステムスレッドとブランチスレッド

 マルチスレッド超ミニBASICでは、「ステムスレッド」と「ブランチスレッド」の2種類のスレッドがあります。「ステム(stem)」は「幹」、「ブランチ(branch)」は「枝」の意味です。一つのプログラムの中に唯1つのステムスレッドと複数のブランチスレッドが存在します。
 RUNしたとき最初に走り出す、一番小さい行番号から始まる処理の流れをステムスレッドと呼びます。START文で起動された処理の流れをブランチスレッドと呼びます。

 ステムスレッドとブランチスレッドではEND文の働きが異なります。ブランチスレッドでEND文を実行するとそのスレッドが終了するだけですが、ステムスレッドでEND文を実行するとプログラム全体が終了します。木の枝を折りとっても幹は倒れませんが、幹を倒せば枝もすべて倒れる、というのをイメージするとわかりやすいです。


ステムスレッドとブランチスレッドのイメージ図

・とりあえずアルファ版

 標準版の超ミニBASIC for H8/3048 をほとんどそのままマルチスレッド化しました。スレッドは4つまで同時に走ります。4つって少ないようですが、しょせんBASICインタプリタですからあまり無理をしないほうがよいでしょう。

 コンテキストスイッチングは文を1つ実行するごとに行われます。
※PRINT文の使用には注意してください。9600ボーだと1文字表示するのに約1ミリ秒かかります。たとえば1つのPRINT文で100文字くらいの文字列を出力すると、約0.1秒の間コンテキストスイッチングは行われません。

・変数は、全スレッドで共通です。たった26個の変数を4つのスレッドでわけ合うのでけっこうキビシイです。次のバージョンではスレッドごとに変数を独立させるとか、何らかの対策をします。
・メモリ、I/Oなどの排他制御は全くしていません。別のスレッドで同じ番地をアクセスして不具合がおこらないよう、注意してプログラムを組みます。
・実は、INPUT文やGETC関数でキー入力待ちになるとすべてのスレッドが止まります。これも次のバージョンでなんとかしたいところです。

マルチスレッド超ミニBASIC for H8/3048 α版をダウンロードする


ダウンロードしたファイルは+Lhaca、Winzipなどの解凍ソフトで解凍してください。
※このアーカイブ中にはソースプログラム(mcmb3048.mar)と実行プログラム(mcmb3048.mot)しか入っていません。このページを見ていない人には使い方がわからないので、他で再配布しないでください。

・動かしてみよう

次のプログラムを動かしてみます。
 10 FOR I=1 TO 10
 20   PRINT "I=",I
 30   IF(I=5) START *THRE1
 40 NEXT
 50 END
100 *THRE1
110 FOR J=1 TO 10
120   PRINT "        J=",J
130 NEXT
140 END

実行結果
I= 1
I= 2
I= 3
I= 4
I= 5
I= 6
        J= 1
I= 7
        J= 2
        J= 3
I= 8
        J= 4
I= 9
        J= 5
        J= 6
I= 10
        J= 7

 ステムスレッドとブランチスレッドが並列に動作しているのがわかると思います。おや、しかしブランチスレッドは最後まで実行されていませんね。これはステムスレッドが先に終了してしまっているからです。ステムスレッドの50番以降を次のように直しましょう。
50 WAIT THREADS(0)<2
60 END

もう一度実行してみましょう。
I= 1
I= 2
I= 3
I= 4
I= 5
I= 6
        J= 1
I= 7
        J= 2
        J= 3
I= 8
        J= 4
I= 9
        J= 5
        J= 6
I= 10
        J= 7
        J= 8
        J= 9
        J= 10

 今度は大丈夫ですね。

・文法の説明

 このページではマルチスレッド関連の文・関数のみ説明します。他の、マルチスレッドとは関係ない文・関数については標準版と同じなのでこちらをご覧ください。

追加された文・関数

《 ENACS文 》

書式:

 コンテキストスイッチングを許可します。複数のスレッドが走っている場合、各スレッドに順に実行権が行き渡るようになります。
 なお、RUN直後はデフォルトでコンテキストスイッチングは「許可」になっています。
 ENACS文はダイレクトモードでは実行できません。

《 DISCS文 》

書式:

 コンテキストスイッチングを禁止します。他のスレッドに割り込まれたくないときに使用します。ENACS文を実行するまで他のスレッドに実行権は移りません。
 DISCS文はダイレクトモードでは実行できません。

《 START文 》

書式:

 ブランチスレッドを起動します。*<ラベル>で指定された行以降がブランチスレッドとして動きます。START文を実行した元スレッドもそのまま実行を続けます。
 START文はダイレクトモードでは実行できません。

《 WAIT文 》

書式:

 <式>の値が0以外になるまで待ちます。コンテキストスイッチが許可になっていれば待っている間も他のスレッドに実行権は行き渡ります。

《 THREADS関数 》

書式:

 現在走っているスレッド数を返します。返る値は1〜4です(ステムスレッドは常に走っているので0が返ることはありえません)
 <式>の値は現バージョンでは無意味ですが、0を指定してください。
 この関数は、すべてのブランチスレッドが終了したのを確認からステムスレッドを終了したい場合などに使います。

標準版と機能が異なる文

《 END文 》

書式:

 ステムスレッドで実行するとプログラム全体が終了します。ブランチスレッドで実行するとそのスレッドだけを終了させ、コンテキストスイッチングを許可にします。

エラーメッセージ

OUT OF THREAD SPACE

すでに4つスレッドが走っているのに、さらにスレッドを起動しようとした

BAD MODE

ENACS文、DISCS文、START文をダイレクトモードで実行しようとした