ChienomiChienomiシェルスクリプトで並列実行制御を行うシェルスクリプトを書く場合において、処理を順次おこないたいことは多いはずだ。多くのディレクトリや処理のリストなど、順に処理を適用していくケース。この設計だが、「ある特定の場所でしか実行されないスクリプト」はnoexecが指定されていない限りはそのルートに設置すべきだと思う。対象ディレクトリごとにスクリプトが異なってくる場合はその対象ディレクトリごとにスクリプトを設置する。なく、その違いがディレクトリのパス自体に含まれるのであれば、単純に対象ディレクトリに空のドットファイルを置けばよいと思う。)doneのようなスクリプトを書けば良い。Gitが「Gitを実行するディレクトリがリポジトリになる」という仕様なので、このように方法にらも随分なれてしまった。${${i:h}:t}doneという方法も考えられる。これは別にディレクトリ単位であった場合に限らない。テキストファイルに1行1エントリ形式で書いて読みながら処理する場合も同様だ。別に最初からターゲットをグループ分けしてもいいし、xargsを使って3つずつ実行する、といったことで並列化することもできる。できれば常に3スレッドで実行する、といったモデルのほうが好ましいのではなかろうか。これを実現するためにシェルスクリプトで並列実行制御したいのだが、残念ながらこれはかなり難しい。Zshでもzthreadがあるような話も目にするのだが、まぁ実際はそうもいかなそうだ。並列実行の難しさは、「同時アクセス」にある。あるリソースに同時にアクセスした場合、いろいろな形でおかしなことになる。done)wait結局read時に同時アクセスするとおかしな値を取ることになり(空文字列だったり、複数行がぐちゃぐちゃに混ざったものだったりする)、ちゃんと動作しない。ここでのポイントをまとめておこう。()はfork子プロセスを生成し、子プロセスで実行する。この子プロセスに渡されたパイプは、子プロセス自体の標準入力として受け取ることになる。リダイレクトしないプロセスはこのファイルデスクリプタを共有する&でバックグラウンドで実行する。子プロセスを生成したかどうかは関係がない外部コマンドはfork+execで子プロセスを生成するが、関数は生成しないwaitはジョブを共有待ち合わせる。引数なしですべてのジョブを待つ同じファイルデスクリプタを共有している場合、IOの位置もいずれかのプロセスが動かせばすべてのプロセスで動く結局、アクセスしたら要素をひとつ返してくれるQueueがほしいのだ。そこでまじめに考えてみた。単純なのはflockを使う方法なのだが単純にはいかない。プロセスの中で処理したければ、ファイルデスクリプタを使った、複雑な方法が必要になる。その中で比較的スマートに処理できると考えたのがGistのスクリプトだ。この場合、.lockは空ファイルであり、単なるロックでしかない。無駄なファイルを作るのにはちょっと抵抗があるが、方法としては比較的簡単だ。この方法はbashでもほぼ同様に書くことができる。zshらしい方法とは言えない。なんか悔しいので、Zshらしい方法として、Socketを使うという方法を提案してみる。UNIXドメインソケットはファイルパスを用いてプロセス間通信を行う。TCP同様、サーバーが接続を受け付け、IOを確立するものだ。一般的にはサーバーは並列処理できるように接続の受け付けはマルチスレッドで行う。&doneだが、シングルスレッドで行った場合はどうなるか。listenはしているがacceptはしていない状態だと接続しようとするclientはブロックされ、acceptされるまで待たされることになる。結果的に、あるリソースにアクセスし、供給する部分がシングルスレッドになる。同時にアクセスしてもproducerはそれをブロックして順番に渡していくことになる。これはごく普通のマルチプロセスモデルであり、Perlはthreadが非推奨で、UNIXドメインソケットの利用を推奨している。そのため特に目新しいものではないのだが、Zshでおこなう(シェルスクリプトで行う)というとまたちょっと味があるのではないか。