Rob Pike谈到了Go语言中的并发编程,其主旨是在分开的轻量级进程中启动并发的任务,任务通过channel通信来同步。Common Lisp中的ChanL库提供了类似的功能,尽管我不知道其线程如何轻量,但其接口是相似的。
使用channel简单有效的例子是并行执行一些IO相关的任务,比如取多个网页。开始前,先定义一个计时的宏:
(defmacro time-it (&body body)
(let ((start-time (gensym)))
`(let ((,start-time (get-internal-real-time)))
,@body
(format t "Runtime: ~a milliseconds.~%" (- (get-internal-real-time) ,start-time)))))
接着使用QuickLisp装入ChanL与Drakma(用于简单的HTTP requests):
(ql:quickload "drakma")
(ql:quickload "chanl")
URLs例子:
(defparameter *urls*
(list
"http://blog.thezerobit.com/"
"http://quicklisp.org/"
"http://www.cliki.net/index"
"http://sbcl.org/"))
一个执行简单HTTP request的函数:
(defun do-request (url)
(let ((start-time (get-internal-real-time)))
(format t "Starting request: ~a~%" url)
(drakma:http-request url)
(let ((elapsed (- (get-internal-real-time) start-time)))
(format t "Completed request in ~a ms: ~a~%" elapsed url))))
没有使用并发的做法:
(time-it (dolist (url *urls*) (do-request url)))
;; Starting request: http://blog.thezerobit.com/
;; Completed request in 616 ms: http://blog.thezerobit.com/
;; Starting request: http://quicklisp.org/
;; Completed request in 819 ms: http://quicklisp.org/
;; Starting request: http://www.cliki.net/index
;; Completed request in 429 ms: http://www.cliki.net/index
;; Starting request: http://sbcl.org/
;; Completed request in 291 ms: http://sbcl.org/
;; Runtime: 2155 milliseconds.
以串行的方式完成所有requests要超过2秒。
(defun do-request-chan (url chan)
(let ((start-time (get-internal-real-time)))
(chanl:send chan (formatnil "Starting request: ~a~%" url))
(drakma:http-request url)
(let ((elapsed (- (get-internal-real-time) start-time)))
(chanl:sendchan
(format nil "Completed request in ~a ms: ~a~%" elapsed url)))))
对于每个HTTP request,我们调用pexec启动分开的进程,然后等待8条消息进入为同步而创建的channel。
(time-it
(let ((chan (make-instance'chanl:channel)))
(dolist (url *urls*)
(chanl:pexec () (do-request-chan url chan)))
(dotimes (x 8)
(format t (chanl:recv chan)))))
;; Starting request: http://blog.thezerobit.com/
;; Starting request: http://quicklisp.org/
;; Starting request: http://sbcl.org/
;; Starting request: http://www.cliki.net/index
;; Completed request in 291 ms: http://blog.thezerobit.com/
;; Completed request in 302 ms: http://www.cliki.net/index
;; Completed request in 306 ms: http://sbcl.org/
;; Completed request in 703 ms: http://quicklisp.org/
;; Runtime: 703 milliseconds.
整个操作仅花费了那个最长request的时间,创建线程的开销与并发节约的时间相比可以忽略。注意,缺省情况下channels不缓存,因此send将阻塞到有其它线程调用recv为止。ChanL也提供了带缓存的channels和其它一些goodies,有兴趣的可以自己研究。