Created
July 3, 2025 03:56
-
-
Save jwiegley/095eb7190b25e079fd673b1cd9bdbe8d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; -*- lexical-binding: t -*- | |
(require 'cl-lib) | |
(let* ((mutex (make-mutex)) | |
(cond-var (make-condition-variable mutex)) | |
waiting | |
(thread-a | |
(make-thread | |
#'(lambda () | |
(message "a0") | |
(with-mutex mutex | |
(message "a1") | |
(setq waiting t) | |
(condition-wait cond-var) | |
(setq waiting nil) | |
(message "a2")) | |
(message "a3")))) | |
(thread-b | |
(make-thread | |
#'(lambda () | |
(message "b0") | |
(with-mutex mutex | |
(message "b1") | |
(condition-notify cond-var) | |
(message "b2")) | |
(message "b3"))))) | |
(message "main1") | |
(let ((countdown 100)) | |
(while (or (thread-live-p thread-a) | |
(thread-live-p thread-b)) | |
(when (and (not (thread-live-p thread-b)) | |
(= 0 (setq countdown (1- countdown)))) | |
(with-mutex mutex | |
(if waiting | |
(thread-signal thread-a 'blocked t)))) | |
(thread-yield))) | |
(message "main2: %S" (thread-last-error t)) | |
(message "main3: %S" (thread-join thread-a)) | |
(message "main4: %S" (thread-join thread-b)) | |
(message "main5: %S" (all-threads))) | |
;; This has a deadlock scenario, because notifications are not "queued", but | |
;; rather sent to the current list of waiting threads at call time: | |
;; | |
;; main1 | |
;; b0 | |
;; b1 | |
;; b2 | |
;; b3 | |
;; a0 | |
;; a1 | |
;; ... | |
;; | |
;; If I reverse the order in which the threads above are created, it ends with | |
;; a `blocked' except the majority of the time. Using the order above it | |
;; succeeds the majority of the time, but the deadlock scenario is still | |
;; possible. | |
;; | |
;; Thus, code must be defensive when using `condition-wait': Waiting threads | |
;; must be satisfied or killed if all notifying threads have completed and the | |
;; waiting thread is still running. However, it's also possible that when the | |
;; waiting thread is the only one remaining, it received the notification and | |
;; is now executing Lisp beyond the wait point. So, to be safely and properly | |
;; defensive, we need to use a global flag under the condition variable's | |
;; mutex AND a timeout to give the waiting thread a chance to naturally finish | |
;; executing if it wasn't in fact blocked. | |
;; | |
;; Another question: Is this also possible? I would be a bit surprised if so, | |
;; but the manual implies this to be the case: | |
;; | |
;; main1 | |
;; a0 | |
;; a1 | |
;; b0 | |
;; b1 | |
;; a2 | |
;; a3 | |
;; b2 | |
;; b3 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment