読者です 読者をやめる 読者になる 読者になる

簡易的なカウントダウンラッチ

sbcl utility parallel

後々使いたいので、簡単なカウントダウンラッチを作成してみた。
ウェイトは単純なスピンで実装しているのであまり実用的ではないけど、お試し用途であれば十分(だと思う)

(defpackage countdown-latch 
  (:use :common-lisp)
  (:export make
           countdown-and-await))
(in-package :countdown-latch)

(defstruct latch 
  (count 0 :type fixnum))

;; 作成
(defun make (count)
  (make-latch :count (max 0 count)))

(defmacro barrier (exp)
  `(sb-thread:barrier (:data-dependency) ,exp))

(defun atomic-decriment-if-plus (latch)
  (let* ((old (latch-count latch))
         (new (1- old)))
    (when (plusp old)
      (unless (eq old (sb-ext:compare-and-swap (latch-count latch) old new))
        (atomic-decriment-if-plus latch)))))

;; 一つカウントダウンした後、カウントが0になるまで待機する
(defun countdown-and-await (latch)
  (atomic-decriment-if-plus latch)
  (loop UNTIL (barrier (zerop (latch-count latch)))))  ; spin wait

使用例:

;;; sbcl-1.0.49(x86_64)
;;; 関数準備

;; メッセージ出力用関数
;; 複数スレッドの出力の混在を防ぐためにミューテックス内でformat関数を呼び出す
(let ((io-mutex (sb-thread:make-mutex)))
  (defun message (fmt &rest args)
    (sb-thread:with-mutex (io-mutex)
      (apply #'format t fmt args))))

;; 各スレッド用の関数
;; 各ラッチ通過毎にメッセージを出力する
(defun do-process (id latches)
  (message "~&; <~a> START~%" id)
  (loop FOR latch IN latches
        FOR i FROM 0
    DO
    (sleep (random 1.0))                          ; 処理時間にばらつきを持たせるために、適当な時間スリープさせる
    (countdown-latch:countdown-and-await latch)   ; カウントダウン & 待機
    (message "~&; <~a> PASSED: latch~d~%" id i))) ; 通過

;; スレッド数とラッチ数を指定してdo-process関数を実行する
(defun test (thread-num latch-num)
  (let ((latches (loop REPEAT latch-num COLLECT (countdown-latch:make thread-num))))
    (loop FOR thread-id FROM 0 BELOW thread-num
          COLLECT
          (sb-thread:make-thread (lambda () (do-process thread-id latches)))
          INTO threads
          FINALLY
          (mapc #'sb-thread:join-thread threads) ; 全スレッドの終了を待機
          (return 'done))))
;;; 実行: スレッド数=5, ラッチ数=3
> (test 5 3)
; <0> START
; <1> START
; <2> START
; <3> START
; <4> START
; <2> PASSED: latch0
; <1> PASSED: latch0
; <3> PASSED: latch0
; <4> PASSED: latch0
; <0> PASSED: latch0 ; 全スレッドがラッチ0を通過
; <4> PASSED: latch1
; <2> PASSED: latch1
; <3> PASSED: latch1
; <1> PASSED: latch1
; <0> PASSED: latch1 ; 全スレッドがラッチ1を通過
; <0> PASSED: latch2
; <4> PASSED: latch2
; <3> PASSED: latch2
; <1> PASSED: latch2
; <2> PASSED: latch2 ; 全スレッドがラッチ2を通過
DONE