今、以下のような構成で CAN メッセージとユーザ入力を処理するサーバプログラムを書いているわけですが

だいぶん前に書いた記事では、メッセージ受信を察知して処理する方法で一番手っ取り早いのは、CAN インタフェースがメッセージを受け取り次第キューに入れて、リクエスト処理部はキューに何か入っていないかチェックし続ける、しかしこれをやると CPU が空回りして無駄に発熱するのでラズパイには良くないという話でした。ではどうするのか?手法は山のようにありますが、Rust でプログラミングをするのは初めてで不慣れで、散々いろんな手を試してはうまく行かず、を繰り返し、どうも Rust 流のやり方があってそれ以外はあまりうまく行かないのだということがだんだんわかってきました。Rust 流とは、チャネルを使う方法です。ここでいろんな手法をどばーんと並べると記事が読みにくくなるので、チャネルのやり方だけ僕の理解したなりにまとめとこうと思います。
チャネルは、C 言語の世界でいうパイプに近いです。チャネルを生成すると「送り手」と「受け手」オブジェクトが作られます。送る側は「送り手」を使って何かメッセージがあれば送り、処理側は「受け手」を使ってメッセージが来るまで CPU を止めて待っていることができます。こういう待ち方をブロッキングと言いますがブロッキングに入ると CPUは他の仕事がなければアイドル状態に入ります。とにかくまあ、チャネルはいい部品です使えそうです。まずはこんな風にしてみます

リクエスト処理部はメッセージが入ってくるまで止まって待っていて、メッセージが来れば処理を実行する。終わったらまたメッセージを待つ、のループで、前回と同じようにポーリングですけれどもうまいこと CPU を浪費せずに待つことができます。
でも、サーバの仕事はこれだけではありません。ユーザからの命令も受けないといけない。これもまた「いつ来るかわからないメッセージ」です。どうやって二系統のいつ来るかわからないメッセージを受け取るかが問題になります。以下はうまく行かない例:「手1と手2のところで交互に待つ」

ユーザコマンドと CAN メッセージのどちらが先に来るかわからないので、例えばユーザコマンドの「受け手」で待っているときに CAN メッセージが来ても次のユーザコマンドが来るまで処理できません。いわゆる処理が「凍り付いた」状態にしょっちゅうなります。
それで、どうしたかというと、チャネルを以下のように三種類用意しました。
- CAN メッセージ渡し用
- ユーザコマンド渡し用
- イベント通知用
ちょっと忙しくなってきたので通知用には猫の手を借りました。

実は、mpsc (multi producers, single consumer) というタイプのチャネルを使うと、「送り手」は複製できてどちらからも「受け手」にメッセージを送ることができます。この複製された送り手を個別に持ち、メッセージやコマンドが発生したら、通常の「送り手」に情報を渡した後、「通知の送り手(猫の手)」に「何のイベントが起きたか」の識別情報を送ります。リクエスト処理部は、通知の受け手だけで待ち受けて、通知が来たら「何のイベントが起きたか」の内容に従って処理を開始します。こうするとどちらのイベントが来てもすぐに対応することができます。
実際には通知は二種類ではなくて今のところ5種類ぐらいありますが、まあこれできちんと想定通りには動いています。ただ、この方法は処理することが多くなってくるとプログラミングがかなり複雑になってくるので、全体の設計を見直しているところではあります。
上の方法はすこし煩雑で、またイベントごとにメッセージを二回送らなくてはいけないのがめんどくさいです。もっと単純な方法はないの?と検討してだめだった方法を最後に二つほど紹介。
ダメ1:「スレッドを立ててユーザコマンドとCANメッセージを個別に待ち受ける」

C++ や Java のプログラミングでは常套手段で、実際リクエスト処理部がイミュータブルなら安全な方法ですが、今回はあてはまりません。その場合 Rust コンパイラはこの手法を受け付けてくれません。Rust の第一の特徴は何といってもメモリの扱い方で、メモリつまりオブジェクトについて「いつもオーナは一人」が徹底しています。スレッドを二本立てると、それならリクエスト処理オブジェクトはどっちのスレッドに属するのか?が問題になりコンパイラは拒否します。処理部がイミュータブルならできる気もしますが「受け手」の持ち方が面倒でコーディングは煩雑になる予感があります。ちなみに Java や C++ ではこれぐらいのことは平気でできますが Java や C++ では安全というわけではなく、危ないコードも書けますというだけのことです。
ダメ2:「コマンド処理とメッセージ処理を完全に分けて行う」

これで済めばこうすれば良いのですが、今回の開発に関してはだめです。ユーザコマンドと CAN メッセージは複雑に絡み合っていて、相互に情報のやり取りが必要です。一つのオブジェクト内に収まってた方が単純で、分けてしまうと情報のやり取りやタイミングの制御などかえってややこしいことになります。