I have a (possibly irrational) bias against mutexes, and attempted to solve a threading problem with just message passing. The code works, but only part of the time.
This code:
(let ((th (thread-start! (make-thread (lambda () (let lp () (let ((p (thread-receive 0 #f))) (if p (p 40) (begin (thread-yield!) (lp)))))))))) (+ 2 (call/cc (lambda (ret) (thread-send th ret) (thread-sleep! 4)))))
Occasionally fails by attempting to add 2 to #!void. Actually, it raises the error, but returns 42 anyway. Seems like I can get away with 4 or 5 evaluations before I hit the failure condition. It seems risky, but I was wondering if someone could explain why? If I increase thread-receive's timeout to 1, it succeeds consistently, but I haven't tried it with lots of threads -- I assume it's a race condition that would reappear.
Where is the #!void coming from? Is the execution of the continuation interrupting the thread-sleep!, but returning the value of the sleep rather than the value passed to the continuation? I'm willing to use mutexes, but I'm curious.
Incidentally, the end goal is a stack of database connections, maintained through mutation. It's intended to be thread-safe, so I was planning on closing over a designated thread to do the mutations, and having it return values to the calling functions through this mechanism. I'll use mutexes if that's the right solution.
Lang
Afficher les réponses par date
On 2/20/07, Lang Martin lang-gb@coptix.com wrote:
I have a (possibly irrational) bias against mutexes, and attempted to solve a threading problem with just message passing. The code works, but only part of the time.
I haven't run your code myself, but I think I see a few things wrong with it.
This code:
(let ((th (thread-start! (make-thread (lambda () (let lp () (let ((p (thread-receive 0 #f))) (if p (p 40) (begin (thread-yield!) (lp)))))))))) (+ 2 (call/cc (lambda (ret) (thread-send th ret) (thread-sleep! 4)))))
Occasionally fails by attempting to add 2 to #!void.
Let's call the original thread "thread #1" and the newly created one "thread #2".
Thread #1 will start thread #2, capture its own continuation and send it to thread #2. Then it adds the result of (thread-sleep! 4) [which is #!void] to 2, which cause your error after 4 seconds.
In thread #2, calling (p 40) will make that thread invoke thread #1's continuation, resulting in a final result of 42. Note that thread #2 will not loop if it gets the continuation as a message, because it discards its own continuation when invoking (p 40). It basically becomes thread #1 at that point.
Other notes: you should just wait in thread #2 for a message instead of waiting in a spin lock (ie using 'thread-receive' with a timeout of 0). Also, calling 'thread-yield!' isn't necessary since Gambit's thread system is preemptive.
Finally, for debugging it might help to use Gambit's command-line options so that threads other than the primordial thread will also start a repl on crashes, using for example: % gsi -:dar (for more details see Gambit's documentation).
Hope this helps and that I didn't misinterpret the code. Personally, I don't think you will have to use mutexes if you design your program correctly.
Guillaume
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On 20-Feb-07, at 10:48 AM, Guillaume Germain wrote:
On 2/20/07, Lang Martin lang-gb@coptix.com wrote: I have a (possibly irrational) bias against mutexes, and attempted to solve a threading problem with just message passing. The code works, but only part of the time.
I haven't run your code myself, but I think I see a few things wrong with it.
Guillaume's analysis is correct. Let me add that perhaps you wanted this:
(define (foo) (let ((result (let ((th (thread-start! (make-thread (lambda () (let loop () (let ((p (thread-receive))) (thread-start! (make-thread (lambda () (p 40))))) (loop))))))) (+ 2 (call/cc (lambda (ret) (thread-send th ret) (thread-send th ret) (thread-send th ret) 111)))))) ;; all threads (except "th") will execute this: (pp result) (thread-sleep! 2)))
(foo)
This code creates the "server" thread "th" to receive continuations. For each continuation received, "th" will create a new thread which resumes this continuation. So "th" can be used as a server for cloning threads.
Marc