/examples/futures.lisp

http://github.com/sykopomp/chanl · Lisp · 92 lines · 52 code · 9 blank · 31 comment · 4 complexity · af5ae93daba5b74b4345943d635913b0 MD5 · raw file

  1. ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
  2. ;;;;
  3. ;;;; ChanL example implementation of doing concurrency using futures instead of channels.
  4. ;;;;
  5. ;;;; Copyright © 2009 Kat Marchan, Adlai Chandrasekhar
  6. ;;;;
  7. ;;;; This file is derived from 'Eager Future'; see the file COPYRIGHT, in the top directory,
  8. ;;;; for the license information for that project.
  9. ;;;;
  10. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  11. (in-package :chanl.examples)
  12. ;;; This example is similar to Eager Future's API.
  13. ;;; It demonstrates the value of channels as concurrency primitives.
  14. (defstruct (future (:print-object (lambda (f s) (print-unreadable-object (f s :type t :identity t)))))
  15. (channel (make-instance 'buffered-channel :size 1) :read-only t))
  16. (define-condition execution-error (error)
  17. ((cause :initarg :cause :reader execution-error-cause)
  18. (future :initarg :future :reader execution-error-future))
  19. (:report (lambda (condition stream)
  20. (format stream "~A errored during execution.~%Cause: ~A"
  21. (execution-error-future condition)
  22. (execution-error-cause condition)))))
  23. (let ((sentinel (make-symbol (format nil "The future has performed an illegal ~
  24. operation and will have to be shut down"))))
  25. (defun yield (future)
  26. "Yield the values returned by FUTURE. If FUTURE isn't ready to yield yet, block until it is."
  27. (let ((yielded-values (recv (future-channel future))))
  28. (send (future-channel future) yielded-values)
  29. (if (eq sentinel (car yielded-values))
  30. (error (cdr yielded-values))
  31. (values-list yielded-values))))
  32. (defun future-call (function &key (initial-bindings *default-special-bindings*)
  33. (name "Anonymous FUTURE"))
  34. "Executes FUNCTION in parallel and returns a future that will yield the return value of
  35. that function. INITIAL-BINDINGS may be provided to create dynamic bindings inside the thread."
  36. (let ((future (make-future)))
  37. (pcall (lambda ()
  38. (send (future-channel future)
  39. (handler-case
  40. (multiple-value-list (funcall function))
  41. (condition (cause)
  42. (cons sentinel (make-condition 'execution-error
  43. :cause cause :future future))))))
  44. :initial-bindings initial-bindings
  45. :name name)
  46. future))
  47. ) ; End sentinel closure
  48. (defmacro future-exec ((&key initial-bindings name) &body body)
  49. "Convenience macro that makes the lambda for you."
  50. `(future-call (lambda () ,@body)
  51. ,@(when initial-bindings `(:initial-bindings ,initial-bindings))
  52. ,@(when name `(:name ,name))))
  53. (defun future-select (&rest futures)
  54. "Blocks until one of the futures in FUTURES (a sequence) is ready to yield,
  55. then returns that future."
  56. ;; This is an improvement. However, we should try to find some way of not "thrashing". - Adlai
  57. (setf futures (sort futures (lambda (a b) a b (zerop (random 2)))))
  58. ;; This is incorrect. SEND/RECV-BLOCKS-P should not be used outside of the internals. - syko
  59. (loop for future = (find-if 'send-blocks-p futures :key 'future-channel)
  60. when future return future))
  61. (defmacro future-let ((&rest bindings) &body body)
  62. (loop for (symbol . forms) in bindings
  63. for future = (make-symbol (string symbol))
  64. collect `(,future (future-exec (:name "FUTURE-LET Worker") ,@forms)) into futures
  65. collect `(,symbol (yield ,future)) into variables
  66. finally (return `(let ,futures (symbol-macrolet ,variables ,@body)))))
  67. ;; EXAMPLES> (defparameter *future* (future-exec () 'success))
  68. ;; *FUTURE*
  69. ;; EXAMPLES> (yield *future*)
  70. ;; SUCCESS
  71. ;; EXAMPLES> (yield (future-select (future-exec () (sleep 10) 'long)
  72. ;; (future-exec () (sleep 2) 'short)))
  73. ;; SHORT
  74. ;; EXAMPLES> (defparameter *future* (future-exec () (error "OHNOES")))
  75. ;; *FUTURE*
  76. ;; EXAMPLES> (yield *future*)
  77. ;; ...
  78. ;; #<FUTURE #x14FFE71E> errored during execution.
  79. ;; Cause: OHNOES
  80. ;; [Condition of type EXECUTION-ERROR]
  81. ;; ...
  82. ;; Invoking restart: Return to SLIME's top level.
  83. ;; ; Evaluation aborted.