マルチスレッドと副作用

(排他制御の件での初歩的なミスに頭を悩ませている最中ではありますが、また別な話題です)


並列処理プログラミングにおける副作用というのは様々な厄介な問題の温床ですが、MzScheme の channel を使うと問題を解決できる場合があります。

スレッド下の処理を、安全な部分とそうでない部分とに分離でき、かつその際主要な処理が安全な側に分離できる場合です。

例えば、ウェブ上から情報を取ってきて、それをローカルのデータベースに記録する、というようなケースを想定しましょう。

この時、後者の処理をサンクとして分離し、あとでまとめてシーケンシャルに処理すれば良いのです:

(for-each (lambda (f) (f)) ; apply thunk
          (threaded-map (lambda (x)
                          (let ((data (get-remote-data x)))
                            ;; return thunk
                            (lambda ()
                              (write-db data))))
                        l))

threaded-map を使用しています。

;; From: http://www.kuro5hin.org/story/2004/3/17/93442/8657
(define (threaded-map f l)
  (map channel-get
       (map (lambda (x)
              (let ((c (make-channel)))
                (thread (lambda ()
                          (channel-put c (f x))))
                c))
            l)))

ただし、これはごくシンプルなアプリケーションでのみ有効な方法と言っておくべきかもしれません。threaded-map で制御されていないスレッドや、他のプロセスによって共有データを変更される可能性は考慮していないからです。


ついでに、HTTP 通信など、大量にスレッド処理を行いたくない場合のために、ちょっとずつに分けて処理する関数を作ってみました:

(define maximum 10)

(define (threaded-map/split f l)
  (cond ((null? l) '())
        ((> (length l) maximum)
         (let-values (((l r) (split-at l maximum))) ; srfi-1
           (append (threaded-map f l)
                   (threaded-map/split f r))))
        (else (threaded-map f l))))