MicroPyton的午睡(10) _thread、マルチスレッドは出来るけれども…

Joseph Halfmoon

このところ、MicroPythonのライブラリを「触って」「実感して」おりますが、今回はマルチスレッドの為の「低水準」ライブラリ _thread であります。フルPythonであれば、この上に高水準な threading が存在するのでありますが、MicroPythonの場合は低水準だけのようです。最低限のものはあるから後は自分でやれ、という感じか。

※「MicroPython的午睡」投稿順 Indexはこちら

(今回使用のサンプルプログラム全文は末尾にあります。)

前回も述べましたが、MicroPythonの実装はPythonに「かなり似せてある」ようですが違いもママあります。マルチスレッドをサポートする _thread モジュールについては以下のMicroPythonのドキュメント

_thread – multithreading support

では、詳しくはフルPythonの _thread のドキュメントを見よ、と言いつつも、1箇所引用させていただくと、

This module is highly experimental and its API is not yet fully settled and not yet described in this documentation.

という塩梅です。何が実装されているのだかは、使う前に調べる必要がありそうです。また、前回も経験しましたように、MicroPythonといっても機種毎、バージョン毎の実装が異なる場合があるので、これにも注意しないとなりません。

手っ取り早く「知る」ためには、まずは import して中身を見てみるのが一番でありましょう。MicroPythonのREPL(MaixPy v0.5.0) からやってみたところは以下のようです。

>>> import _thread
>>> help(_thread)
object <module '_thread'> is of type module
__name__ -- _thread
LockType -- <class 'lock'>
get_ident -- <function>
stack_size -- <function>
start_new_thread -- <function>
exit -- <function>
allocate_lock -- <function>

これとフルPythonの _thread のドキュメントを比べると、不足が明らかです。特にスレッド間で同期をとるためのLOCK(mutexあるいはバイナリセマフォ)は allocate できるのに、acquireとかreleaseとか操作のためのメソッドが見当たりません。とりあえず新にスレッドを開始するための

start_new_thread()

は存在するので、「細かい話は自分でやれ」という感じでしょうか。また、フルPythonであれば、_threadを直接使わず、高水準な threading を使用することで、スレッド・ローカル・ストレージ(TLS)とか、スレッドの終了を待ち合わせるjoin()なども使えるのですが、当然ながらそれらも不在です。get_ident()があればスレッドのIDが分かるので、TLSが欲しければ自力でID毎の「ストレージ」を作れ、ということでしょう。

また、Pythonの場合、グローバル・インタプリタ・ロック(GIL)の制約があるので、マルチスレッドは組み込み用途などでIO待ちなどが発生するときにこそ有効だと思います。そのようなケースでは、スレッドを使うのではなく、コルーチンである asyncio を使うというのも代案としてはあり得ます。以下では threading と asyncio を比較しています。

IoT何をいまさら(79) Python, threadingとasyncioなLチカ

調べると ドキュメント上ではMicroPythonでも asyncio モジュールは存在しているのですが、今回テストに使っている

MaixPy(RISV-V 64搭載のSoCであるK210用のMicroPython実装) v0.5.0

には asyncio は実装されていないようです。_thread を使うしかありませぬ。末尾に掲げたマルチスレッドのサンプルプログラムを走らせたところを以下に示します。

MAIN: 1
T1 running: 1
T2 running: 1
T1 running: 2
T2 running: 2
MAIN: 2
T1 running: 3
T2 running: 3
T1 running: 4
T2 running: 4
~途中略~
MAIN: 9
T1 running: 17
T2 running: 16
T1 running: 18
T2 running: 17
MAIN: 10
T1 running: 19
T2 running: 18
T1 running: 20
T2 running: 19
Waiting T1...
Done...

MAINスレッドからT1とT2の2つのスレッドを走らせた後、MAINスレッドからの指示で2つの子スレッドの実行を終了させ、それらの終了をMAINスレッドで一応確認して “Done…” という具合です。

あまり難しいことをしなければ、とりあえずIO処理などを別スレッド化するのはOKですかね。

MicroPython的午睡(9) ulabで連立方程式を解く、機種固有実装の蹉跌 へ戻る

MicroPython的午睡(11) ラズパイPico、Thonny IDEで動作確認 へ進む

MicroPython用 マルチスレッドのサンプルプログラム

(MaixPy MicroPython v0.5.0)

import _thread
import time

t1enable = True
t2enable = True
t1exit = False
t2exit = False

def thread1():
    global t1enable, t1exit
    loopCounter=0
    while t1enable:
        loopCounter += 1
        print("T1 running:", loopCounter)
        time.sleep_ms(500)
    t1exit = True
    _thread.exit()

def thread2():
    global t2enable, t2exit
    loopCounter=0
    while t2enable:
        loopCounter += 1
        print("T2 running:", loopCounter)
        time.sleep_ms(550)
    t2exit = True
    _thread.exit()

_thread.start_new_thread(thread1,())
_thread.start_new_thread(thread2,())

mainLoop=0
while mainLoop < 10:
    mainLoop += 1
    print("MAIN:", mainLoop)
    time.sleep_ms(1000)
t1enable = False
t2enable = False
while not t1exit:
   print("Waiting T1...")
   time.sleep_ms(1000)
while not t2exit:
   print("Waiting T2...")
   time.sleep_ms(1000)
print("Done...")