MicroPythonにおけるスレッドと非同期処理について

今回はMicroPythonにおける_threadとasyncioについて説明していきます．

スレッドと非同期処理の違いがあります。

どちらも並行処理を行うことが可能です。しかしその用途には注意が必要です。

# スレッド (Threads)
スレッドは、プロセス内で独立して実行される一連の命令です。マルチスレッドプログラムでは、複数のスレッドを同時に実行することが出来ます。

#### 利点:
マルチコアCPUの利用によるパフォーマンス向上。
同一プロセス内でのスレッド間通信が高速。

#### 欠点:
スレッド同士がお互いの作業を終わるまで待つことによるデッドロックが発生しやすい。
スレッドの管理コスト（コンテキストスイッチ）がかかる。


# 非同期処理 (Asynchronous Programming)
非同期処理は、プログラムが特定のタスクの完了を待たずに他のタスクをバックグラウンドで続けることができる手法です。更に非同期処理はシングルスレッドで動きます。 

#### 利点:

非同期I/O操作の効率的な処理。
シングルスレッドモデルでのデッドロックの回避。
#### 欠点:

コードの複雑化（コールバック地獄など）。
デバッグやエラーハンドリングが難しい。

### まとめ
スレッドは、同時に複数の処理を実行するための手法であり、特に計算集約的なタスクに適しています。
非同期処理は、I/O操作などの待機時間が長いタスクを効率的に処理するための手法です。

# MicroPythonで実装してみた

次にMicroPythonでスレッドと非同期処理を試してみました。
実行環境はESP32というマイクロコントローラを使用します。MicroPythonのファームウェアは、v1.22.2を使用しています。



## MicroPythonのスレッド
今回のプログラムは100msごと10回表示していくというものになります。
またスレッドはバックグラウンドで動いているため
```python
utime.sleep(2)
```
でスレッドが終わるのを待ってください

```python

import _thread
import utime

def print_numbers_thread():
    start_time = utime.ticks_ms()
    for i in range(1,11):
        print(f"{i} at {utime.ticks_diff(utime.ticks_ms(), start_time)} ms")
        utime.sleep(0.1)  # 100ms待機

# スレッドを開始
_thread.start_new_thread(print_numbers_thread, ())

# メインスレッドを待機させるために適当な時間待つ
utime.sleep(2)#バックグラウンドで回っているから
```

実行結果です。
だいたい100msごとに実行されていますね。
```
1 at 0 ms
2 at 102 ms
3 at 202 ms
4 at 303 ms
5 at 403 ms
6 at 504 ms
7 at 604 ms
8 at 705 ms
9 at 805 ms
10 at 906 ms
```

# MicroPythonの非同期処理
非同期処理も100msごとに10回表示していきます。

```python

import uasyncio as asyncio
import utime

async def print_numbers_async():
    start_time = utime.ticks_ms()
    for i in range(1,11):
        print(f"{i} at {utime.ticks_diff(utime.ticks_ms(), start_time)} ms")
        await asyncio.sleep(0.1)  # 100ms待機

async def main():
    await print_numbers_async()

asyncio.run(main())
```

実行結果です。
スレッドと違いだいたい110msで表示されていますね。
```
1 at 0 ms
2 at 111 ms
3 at 221 ms
4 at 331 ms
5 at 441 ms
6 at 551 ms
7 at 661 ms
8 at 771 ms
9 at 881 ms
10 at 991 ms
```
# 2つを組み合わせてみた
スレッドと非同期処理では表示に差があるので組み合わせてより違いを明確化していきます。

```python
import uasyncio as asyncio
import _thread
import utime

# 非同期処理用関数
async def print_numbers_async():
    start_time = utime.ticks_ms()
    for i in range(1,11):
        print(f"Async {i} at {utime.ticks_diff(utime.ticks_ms(), start_time)} ms")
        await asyncio.sleep(0.1)  # 100ms待機

# スレッド用関数
def print_numbers_thread():
    start_time = utime.ticks_ms()
    for i in range(1,11):
        print(f"Thread {i} at {utime.ticks_diff(utime.ticks_ms(), start_time)} ms")
        utime.sleep(0.1)  # 100ms待機

# メイン関数
async def main():
    # スレッドを開始
    _thread.start_new_thread(print_numbers_thread, ())

    # 非同期タスクを実行
    await print_numbers_async()

# イベントループを実行
asyncio.run(main())
```

実行結果です。やはりスレッド➡非同期処理の順で実行されていますね。

```
Async 1 at 0 ms
Thread 1 at 0 ms
Thread 2 at 101 ms
Async 2 at 108 ms
Thread 3 at 202 ms
Async 3 at 218 ms
Thread 4 at 303 ms
Async 4 at 328 ms
Thread 5 at 404 ms
Async 5 at 438 ms
Thread 6 at 504 ms
Async 6 at 548 ms
Thread 7 at 605 ms
Async 7 at 658 ms
Thread 8 at 706 ms
Async 8 at 768 ms
Thread 9 at 807 ms
Async 9 at 878 ms
Thread 10 at 908 ms
Async 10 at 988 ms
```

# まとめ

スレッドは実行処理が早いがデッドロックの可能性があり、非同期処理は実行処理が遅いがシングルスレッドのためデッドロックの可能性がありません。
違いを理解した上で使い分けをするとよりよい実装が出来るのでやってみてください

# MicroPythonのスレッドに関する注意事項

MicroPythonのスレッドのAPIはまだ実験段階みたいです。詳しくは下記のMicroPythonのホームページを参照してください。

https://micropython-docs-ja.readthedocs.io/ja/latest/library/_thread.html

# 参考にしたサイト一覧

https://www.rworks.jp/system/system-column/sys-entry/21730/


https://wa3.i-3-i.info/word12453.html


https://qiita.com/motoki418/items/bfab2c7b55ded7efe1a5


https://atmarkit.itmedia.co.jp/ait/articles/0503/12/news025.html