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
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