前回は2コア平行動作でコア間FIFO通信を使用。FIFOは制御がお楽。しかしその先を考えると排他処理など必要じゃないかと。一応PicoのRP2040にもPico2のRP2350にもSIOブロック内にハードウエアSpinlockというものがあるのだけれども、これってどうよ?その前に_ThreadモジュールのLOCKか?
※Pico関係投稿一覧は こちら 『Pico三昧』は一覧の末尾付近にひっそりと。
※Pico2対応のMicroPython処理系(バイナリ、uf2形式)は以下のURLからダウンロード可能です。
https://micropython.org/download/RPI_PICO2/
※動作確認に使用しているMicroPython処理系は以下です。
MicroPython-1.24.0-riscv–with-newlib4.3.0
RP2040、RP2350排他制御
前々回あたりの処理例が「手抜き」であった一つの理由は、2コアがてんでんバラバラに走っている中で、双方とも勝手にprintしまくったりしているところです。前々回あたりは簡単な出力なので「たまたま」それらしく出力されとりましたが、実際には無関係な2つのprint結果が「混ざって」しまって収拾がつかなくなります。その例題が以下に。
import time, machine, _thread from micropython import const def taskA(): cnt = 0 while cnt < 10: print("Task A.") for i in range(1, 10): print(i+100," ",end="") print() time.sleep(0.5) cnt += 1 def taskB(): cnt = 0 while cnt < 20: print("Task B.") for i in range(1, 10): print(i+200," ",end="") print() time.sleep(0.5) cnt += 1 print("END.") def main(): print("Thread NO LOCK test:") _thread.start_new_thread(taskA, ()) taskB() if __name__ == "__main__": main()
上記を走らせると、Task A は 100, 101と100番台の数字を出力し、Task Bは 200番台の数字を出力するハズですが、結果(一部)は以下のとおり。
100台と200台、そしてスペースなどの出力が混然一体と混じり合いなんだか分からなくなります。それでも誤動作していないように見えるのは立派?
まあこれを交通整理するのにまず考えられるのはAがprintしている間はBに待ってもらう、Bがprintしている間はAに待ってもらうという「排他制御」です。
現状、マルチコアのRP2040、RP2350での排他制御についてのお惚け老人の認識は以下の通りです。
-
- RP2040搭載のArmの末弟、Arm Cortex M0+はATOMICなメモリ操作を行う命令を持っていない(ソフトで排他制御実現できる筈だけれどもメンドイ)
- しかし、RP2040はマルチコア機なので代替手段が欲しかったらしく、SIOブロック内にハードウエアSpinlock機構が存在する
- 一方、RP2350の搭載コアであるArm Cortex M33およびRISC-V Hazard3、両者ともATOMICなメモリ操作を行う命令を持つ(排他制御がお楽。)
- よってRP2350の場合、SIOブロックのハードウエアSpinlock機構なしでもOKよ。
- けれども「RP2040互換性」のため、ハードウエアSpinlock機構はRP2350でも健在。
両者のSIO(Single Cycle IO)比較については以下の過去回をご参照くだされ。
Pico三昧(34) ラズパイPico2:RP2350、Pico:RP2040、SIO比較
さらに今回は、MicroPython処理系上で実験しているので、MircoPython側のご事情も勘案しておかねばなりませぬ。MicroPython側の排他制御については以下の別シリーズ過去回にてRP2040上で既に実験しております。
MicroPython的午睡(13) ラズパイPico、マルチコアの排他制御など
かいつまむとこんな感じ。
-
- MicroPythonの_threadモジュールには排他制御APIが存在
- 排他制御APIはRP2040のハードウエアSpinlock機構は使用していない(ように見える)
- しかしMicroPython処理系内部でハードウエアSpinlock機構を使用している形跡がある
直接ハードウエアアクセスでRP2350上でもハードウエアSpinlock機構を使ってみたいと思うのですが、それはまた次回ね。今回は、MicroPythonの_threadモジュールが持つLOCK機構を使って先ほどのダメダメな例を交通整理してみたいと思います。
_TheadモジュールのLOCKを使ってみた例
実験用のコードが以下に。念のため、ハードウエアSpinlockのステータスレジスタのダンプも入ってます。もし使っていたら1が立っている筈。
import time, machine, _thread from micropython import const SIO_BASE = const(0xd0000000) SIO_NONSEC_BASE = const(0xd0020000) SPINLOCK_ST = const(0x05c) def taskA(): global lock cnt = 0 while cnt < 10: lockP = lock.acquire(1, -1) #wait lock forever if not lockP: print("Task A can not get the lock.") _thread.exit() print("Task A gets lock.") for i in range(1, 10): print(i+100," ",end="") print() print("Task A SPINLOCK_ST: {0:08x}".format(machine.mem32[SIO_NONSEC_BASE + SPINLOCK_ST])) lock.release() time.sleep(0.5) cnt += 1 _thread.exit() def taskB(): global lock cnt = 0 while cnt < 20: lockP = lock.acquire(1, -1) #wait lock forever if not lockP: print("Task B can not get the lock.") return print("Task B gets lock.") for i in range(1, 10): print(i+200," ",end="") print() print("Task B SPINLOCK_ST: {0:08x}".format(machine.mem32[SIO_NONSEC_BASE + SPINLOCK_ST])) lock.release() time.sleep(0.5) cnt += 1 print("END.") def main(): global lock print("Thread LOCK test:") lock = _thread.allocate_lock() _thread.start_new_thread(taskA, ()) taskB() if __name__ == "__main__": main()
ちゃんと排他制御されとるみたい。そしてハードウエアSPINLOCKは使われていない、ここでは。