threads.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. ;;;; Copyright (C) 1996, 1998, 2001, 2002, 2003, 2006, 2010, 2011,
  2. ;;;; 2012, 2018 Free Software Foundation, Inc.
  3. ;;;;
  4. ;;;; This library is free software; you can redistribute it and/or
  5. ;;;; modify it under the terms of the GNU Lesser General Public
  6. ;;;; License as published by the Free Software Foundation; either
  7. ;;;; version 3 of the License, or (at your option) any later version.
  8. ;;;;
  9. ;;;; This library is distributed in the hope that it will be useful,
  10. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;;;; Lesser General Public License for more details.
  13. ;;;;
  14. ;;;; You should have received a copy of the GNU Lesser General Public
  15. ;;;; License along with this library; if not, write to the Free Software
  16. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. ;;;;
  18. ;;;; ----------------------------------------------------------------
  19. ;;;; threads.scm -- User-level interface to Guile's thread system
  20. ;;;; 4 March 1996, Anthony Green <green@cygnus.com>
  21. ;;;; Modified 5 October 1996, MDJ <djurfeldt@nada.kth.se>
  22. ;;;; Modified 6 April 2001, ttn
  23. ;;;; ----------------------------------------------------------------
  24. ;;;;
  25. ;;; Commentary:
  26. ;; This module is documented in the Guile Reference Manual.
  27. ;;; Code:
  28. (define-module (ice-9 threads)
  29. #:use-module (ice-9 match)
  30. ;; These bindings are marked as #:replace because when deprecated code
  31. ;; is enabled, (ice-9 deprecated) also exports these names.
  32. ;; (Referencing one of the deprecated names prints a warning directing
  33. ;; the user to these bindings.) Anyway once we can remove the
  34. ;; deprecated bindings, we should use #:export instead of #:replace
  35. ;; for these.
  36. #:replace (call-with-new-thread
  37. yield
  38. cancel-thread
  39. join-thread
  40. thread?
  41. make-mutex
  42. make-recursive-mutex
  43. lock-mutex
  44. try-mutex
  45. unlock-mutex
  46. mutex?
  47. mutex-owner
  48. mutex-level
  49. mutex-locked?
  50. make-condition-variable
  51. wait-condition-variable
  52. signal-condition-variable
  53. broadcast-condition-variable
  54. condition-variable?
  55. current-thread
  56. all-threads
  57. thread-exited?
  58. total-processor-count
  59. current-processor-count)
  60. #:export (begin-thread
  61. make-thread
  62. with-mutex
  63. monitor
  64. parallel
  65. letpar
  66. par-map
  67. par-for-each
  68. n-par-map
  69. n-par-for-each
  70. n-for-each-par-map
  71. %thread-handler))
  72. ;; Note that this extension also defines %make-transcoded-port, which is
  73. ;; not exported but is used by (rnrs io ports).
  74. (eval-when (expand eval load)
  75. (load-extension (string-append "libguile-" (effective-version))
  76. "scm_init_ice_9_threads"))
  77. (define-syntax-rule (with-mutex m e0 e1 ...)
  78. (let ((x m))
  79. (dynamic-wind
  80. (lambda () (lock-mutex x))
  81. (lambda () (begin e0 e1 ...))
  82. (lambda () (unlock-mutex x)))))
  83. (define cancel-tag (make-prompt-tag "cancel"))
  84. (define (cancel-thread thread . values)
  85. "Asynchronously interrupt the target @var{thread} and ask it to
  86. terminate, returning the given @var{values}. @code{dynamic-wind} post
  87. thunks will run, but throw handlers will not. If @var{thread} has
  88. already terminated or been signaled to terminate, this function is a
  89. no-op."
  90. (system-async-mark
  91. (lambda ()
  92. (catch #t
  93. (lambda ()
  94. (apply abort-to-prompt cancel-tag values))
  95. (lambda _
  96. (error "thread cancellation failed, throwing error instead???"))))
  97. thread))
  98. (define thread-join-data (make-object-property))
  99. (define %thread-results (make-object-property))
  100. (define* (call-with-new-thread thunk #:optional handler)
  101. "Call @code{thunk} in a new thread and with a new dynamic state,
  102. returning a new thread object representing the thread. The procedure
  103. @var{thunk} is called via @code{with-continuation-barrier}.
  104. When @var{handler} is specified, then @var{thunk} is called from within
  105. a @code{catch} with tag @code{#t} that has @var{handler} as its handler.
  106. This catch is established inside the continuation barrier.
  107. Once @var{thunk} or @var{handler} returns, the return value is made the
  108. @emph{exit value} of the thread and the thread is terminated."
  109. (let ((cv (make-condition-variable))
  110. (mutex (make-mutex))
  111. (thunk (if handler
  112. (lambda () (catch #t thunk handler))
  113. thunk))
  114. (thread #f))
  115. (define (call-with-backtrace thunk)
  116. (let ((err (current-error-port)))
  117. (catch #t
  118. (lambda () (%start-stack 'thread thunk))
  119. (lambda _ (values))
  120. (lambda (key . args)
  121. ;; Narrow by three: the dispatch-exception,
  122. ;; this thunk, and make-stack.
  123. (let ((stack (make-stack #t 3)))
  124. (false-if-exception
  125. (begin
  126. (when stack
  127. (display-backtrace stack err))
  128. (let ((frame (and stack (stack-ref stack 0))))
  129. (print-exception err frame key args)))))))))
  130. (with-mutex mutex
  131. (%call-with-new-thread
  132. (lambda ()
  133. (call-with-values
  134. (lambda ()
  135. (call-with-prompt cancel-tag
  136. (lambda ()
  137. (lock-mutex mutex)
  138. (set! thread (current-thread))
  139. ;; Rather than use the 'set!' syntax here, we use the
  140. ;; underlying 'setter' generic function to set the
  141. ;; 'thread-join-data' property on 'thread'. This is
  142. ;; because 'set!' will try to resolve 'setter' in the
  143. ;; '(guile)' module, which means acquiring the
  144. ;; 'autoload' mutex. If the calling thread is
  145. ;; already holding that mutex, this will result in
  146. ;; deadlock. See <https://bugs.gnu.org/62691>.
  147. ((setter thread-join-data) thread (cons cv mutex))
  148. (signal-condition-variable cv)
  149. (unlock-mutex mutex)
  150. (call-with-unblocked-asyncs
  151. (lambda () (call-with-backtrace thunk))))
  152. (lambda (k . args)
  153. (apply values args))))
  154. (lambda vals
  155. (lock-mutex mutex)
  156. ;; Probably now you're wondering why we are going to use
  157. ;; the cond variable as the key into the thread results
  158. ;; object property. It's because there is a possibility
  159. ;; that the thread object itself ends up as part of the
  160. ;; result, and if that happens we create a cycle whereby
  161. ;; the strong reference to a thread in the value of the
  162. ;; weak-key hash table used by the object property prevents
  163. ;; the thread from ever being collected. So instead we use
  164. ;; the cv as the key. Weak-key hash tables, amirite?
  165. (set! (%thread-results cv) vals)
  166. (broadcast-condition-variable cv)
  167. (unlock-mutex mutex)
  168. (apply values vals)))))
  169. (let lp ()
  170. (unless thread
  171. (wait-condition-variable cv mutex)
  172. (lp))))
  173. thread))
  174. (define* (join-thread thread #:optional timeout timeoutval)
  175. "Suspend execution of the calling thread until the target @var{thread}
  176. terminates, unless the target @var{thread} has already terminated."
  177. (match (thread-join-data thread)
  178. (#f (error "foreign thread cannot be joined" thread))
  179. ((cv . mutex)
  180. (lock-mutex mutex)
  181. (let lp ()
  182. (cond
  183. ((%thread-results cv)
  184. => (lambda (results)
  185. (unlock-mutex mutex)
  186. (apply values results)))
  187. ((if timeout
  188. (wait-condition-variable cv mutex timeout)
  189. (wait-condition-variable cv mutex))
  190. (lp))
  191. (else
  192. (unlock-mutex mutex)
  193. timeoutval))))))
  194. (define* (try-mutex mutex)
  195. "Try to lock @var{mutex}. If the mutex is already locked, return
  196. @code{#f}. Otherwise lock the mutex and return @code{#t}."
  197. (lock-mutex mutex 0))
  198. ;;; Macros first, so that the procedures expand correctly.
  199. (define-syntax-rule (begin-thread e0 e1 ...)
  200. (call-with-new-thread
  201. (lambda () e0 e1 ...)
  202. %thread-handler))
  203. (define-syntax-rule (make-thread proc arg ...)
  204. (call-with-new-thread
  205. (lambda () (proc arg ...))
  206. %thread-handler))
  207. (define monitor-mutex-table (make-hash-table))
  208. (define monitor-mutex-table-mutex (make-mutex))
  209. (define (monitor-mutex-with-id id)
  210. (with-mutex monitor-mutex-table-mutex
  211. (or (hashq-ref monitor-mutex-table id)
  212. (let ((mutex (make-mutex)))
  213. (hashq-set! monitor-mutex-table id mutex)
  214. mutex))))
  215. (define-syntax monitor
  216. (lambda (stx)
  217. (syntax-case stx ()
  218. ((_ body body* ...)
  219. (let ((id (datum->syntax #'body (gensym))))
  220. #`(with-mutex (monitor-mutex-with-id '#,id)
  221. body body* ...))))))
  222. (define (thread-handler tag . args)
  223. (let ((n (length args))
  224. (p (current-error-port)))
  225. (display "In thread:" p)
  226. (newline p)
  227. (if (>= n 3)
  228. (display-error #f
  229. p
  230. (car args)
  231. (cadr args)
  232. (caddr args)
  233. (if (= n 4)
  234. (cadddr args)
  235. '()))
  236. (begin
  237. (display "uncaught throw to " p)
  238. (display tag p)
  239. (display ": " p)
  240. (display args p)
  241. (newline p)))
  242. #f))
  243. ;;; Set system thread handler
  244. (define %thread-handler thread-handler)
  245. (use-modules (ice-9 futures))
  246. (define-syntax parallel
  247. (lambda (x)
  248. (syntax-case x ()
  249. ((_ e0 ...)
  250. (with-syntax (((tmp0 ...) (generate-temporaries (syntax (e0 ...)))))
  251. #'(let ((tmp0 (future e0))
  252. ...)
  253. (values (touch tmp0) ...)))))))
  254. (define-syntax-rule (letpar ((v e) ...) b0 b1 ...)
  255. (call-with-values
  256. (lambda () (parallel e ...))
  257. (lambda (v ...)
  258. b0 b1 ...)))
  259. (define (par-mapper mapper cons)
  260. (lambda (proc . lists)
  261. (let loop ((lists lists))
  262. (match lists
  263. (((heads tails ...) ...)
  264. (let ((tail (future (loop tails)))
  265. (head (apply proc heads)))
  266. (cons head (touch tail))))
  267. (_
  268. '())))))
  269. (define par-map (par-mapper map cons))
  270. (define par-for-each (par-mapper for-each (const *unspecified*)))
  271. (define (n-par-map n proc . arglists)
  272. (let* ((m (make-mutex))
  273. (threads '())
  274. (results (make-list (length (car arglists))))
  275. (result results))
  276. (do ((i 0 (+ 1 i)))
  277. ((= i n)
  278. (for-each join-thread threads)
  279. results)
  280. (set! threads
  281. (cons (begin-thread
  282. (let loop ()
  283. (lock-mutex m)
  284. (if (null? result)
  285. (unlock-mutex m)
  286. (let ((args (map car arglists))
  287. (my-result result))
  288. (set! arglists (map cdr arglists))
  289. (set! result (cdr result))
  290. (unlock-mutex m)
  291. (set-car! my-result (apply proc args))
  292. (loop)))))
  293. threads)))))
  294. (define (n-par-for-each n proc . arglists)
  295. (let ((m (make-mutex))
  296. (threads '()))
  297. (do ((i 0 (+ 1 i)))
  298. ((= i n)
  299. (for-each join-thread threads))
  300. (set! threads
  301. (cons (begin-thread
  302. (let loop ()
  303. (lock-mutex m)
  304. (if (null? (car arglists))
  305. (unlock-mutex m)
  306. (let ((args (map car arglists)))
  307. (set! arglists (map cdr arglists))
  308. (unlock-mutex m)
  309. (apply proc args)
  310. (loop)))))
  311. threads)))))
  312. ;;; The following procedure is motivated by the common and important
  313. ;;; case where a lot of work should be done, (not too much) in parallel,
  314. ;;; but the results need to be handled serially (for example when
  315. ;;; writing them to a file).
  316. ;;;
  317. (define (n-for-each-par-map n s-proc p-proc . arglists)
  318. "Using N parallel processes, apply S-PROC in serial order on the results
  319. of applying P-PROC on ARGLISTS."
  320. (let* ((m (make-mutex))
  321. (threads '())
  322. (no-result '(no-value))
  323. (results (make-list (length (car arglists)) no-result))
  324. (result results))
  325. (do ((i 0 (+ 1 i)))
  326. ((= i n)
  327. (for-each join-thread threads))
  328. (set! threads
  329. (cons (begin-thread
  330. (let loop ()
  331. (lock-mutex m)
  332. (cond ((null? results)
  333. (unlock-mutex m))
  334. ((not (eq? (car results) no-result))
  335. (let ((arg (car results)))
  336. ;; stop others from choosing to process results
  337. (set-car! results no-result)
  338. (unlock-mutex m)
  339. (s-proc arg)
  340. (lock-mutex m)
  341. (set! results (cdr results))
  342. (unlock-mutex m)
  343. (loop)))
  344. ((null? result)
  345. (unlock-mutex m))
  346. (else
  347. (let ((args (map car arglists))
  348. (my-result result))
  349. (set! arglists (map cdr arglists))
  350. (set! result (cdr result))
  351. (unlock-mutex m)
  352. (set-car! my-result (apply p-proc args))
  353. (loop))))))
  354. threads)))))
  355. ;; Now that thread support is loaded, make module autoloading
  356. ;; thread-safe.
  357. (set! (@ (guile) call-with-module-autoload-lock)
  358. (let ((mutex (make-mutex 'recursive)))
  359. (lambda (thunk)
  360. (with-mutex mutex
  361. (thunk)))))
  362. ;;; threads.scm ends here