Page 1 of 2

Bordeaux Threads Questions

Posted: Thu Jun 05, 2014 6:05 pm
by macrolyte
Hey.
I'm working through the concurrency chapter in Lisp Outside the Box found here. I'm using Bordeaux threads instead of Allegro CL.

Code: Select all

(defvar *k0* nil)
(setf *k0* (bt:make-thread ;; capture returned thread object
                 (lambda()
                   (unwind-protect (loop)
                     (print "Goodbye" #.*standard-output*))) :name "Say goodbye"))
What is the purpose of the sharp-dot read macro here; I know that it's returning the *standard-output* object at read time, I just don't get why/how it's being done?

Also in the given example for an event loop:

Code: Select all

(defun improved-event-loop () ;; ACL code, not Bordeaux threads!
    (loop
       (process-wait "Waiting for something to happen" 'something-has-happened)
       (act-on-that-thing)))
Is there a way to get equivalent behavior with Bordeaux threads despite the absence of thread-wait in its API? Thanks.

Re: Bordeaux Threads Questions

Posted: Fri Jun 06, 2014 5:55 am
by pjstirling
For your second question, you want BORDEAUX-THREADS:JOIN-THREAD.

On the first question, obviously it is so as to use a "useful" (for some definition of useful) value.

I'm a bit puzzled myself for the exact reason, I think it could be one of two things:

For REPL input, slime binds *STANDARD-OUTPUT* to a stream that sends output to the REPL on the emacs side, something you may notice if you try to TRACE a function that is called by a hunchentoot handler is that the output is NOT passed to the REPL, instead it gets sent to the *inferior-lisp" buffer (unless you tell slime to globally-redirect-io).

My second possibile answer is due to the difference between how global variables in C etc, and special variables in common-lisp work:

In C/C++ global variables, and java/c# static variables at any given time there is only a single value, you can only read it, or mutate it.

When you DEFVAR a special variable you give it a value, and you can SETF it to change it. So far, so boring. Where it becomes more interesting is when you use LET to temporarily bind a new value to the special.

In a lisp without threads you could save the old value, SETF it to the new value, and then when the LET goes out of scope, you can SETF the value back to the saved value.

This doesn't work very well in a threaded lisp though, because with two threads that bind the same special, you would have a very timing sensitive non-determinism on what the value was when the two threads were finished. So threaded lisps use a traditional global variable for the root binding of a special which can be SETF'd and is shared by current and future threads, but when you use LET to rebind it, the binding is thread-local.

Re: Bordeaux Threads Questions

Posted: Fri Jun 06, 2014 6:23 am
by pjstirling
On further thought, for the second possibility, while what I said is true, it doesn't apply in this particular case, so I think it is probably the first possibility (or something more exotic).

Re: Bordeaux Threads Questions

Posted: Fri Jun 06, 2014 9:27 am
by Goheeca
For the process-wait, seems to me, are condition variables more appropriate than a simple join-thread.

Re: Bordeaux Threads Questions

Posted: Sat Jun 07, 2014 2:28 pm
by macrolyte

Code: Select all

(defparameter *lock* (bt:make-lock))
(defparameter *cv* (bt:make-condition-variable))
(defparmeter place ())

(defun try-with-lock-held (place)
    (bt:make-thread (lambda()
                       (bt:with-lock-held (*lock*) ;; should acquire AND release lock?
                         (setf place "done")
                         (bt:condition-notify *cv*))) :name "with-lock-held")) ==>TRY-WITH-LOCK-HELD

(try-with-lock-held place)

place ==> nil
Before I try condtion-wait , what am I doing wrong here?

Re: Bordeaux Threads Questions

Posted: Sat Jun 07, 2014 2:59 pm
by Goheeca
Functions have own scope, it's the same thing like:

Code: Select all

(let ((place place))
  (setf place "done"))

Re: Bordeaux Threads Questions

Posted: Sat Jun 07, 2014 3:32 pm
by Goheeca
For a better comprehension, try:

Code: Select all

(defun try-with-lock-held (place)
  (setf place "done"))
I think, a light introduction would not hurt.

Re: Bordeaux Threads Questions

Posted: Sat Jun 07, 2014 3:59 pm
by macrolyte

Code: Select all

(defparameter *lock* (bt:make-lock))
(defparameter *cv* (bt:make-condition-variable))
(defparameter *place* ())

(defun try-with-lock-held ()
    (bt:make-thread (lambda()
                       (bt:with-lock-held (*lock*) ;; 
                         (setf *place* "done")
                         (bt:condition-notify *cv*))) :name "with-lock-held")) ==>TRY-WITH-LOCK-HELD

(try-with-lock-held)

*place* ==> "done"

Done in by scope...(smh). Thanks!

Re: Bordeaux Threads Questions

Posted: Sun Jun 08, 2014 4:10 pm
by macrolyte
A couple more questions (please see code below):

Code: Select all

;; is this a correct way for this?

(defun test() ;; Is the general pattern to use a defun? Couldn't you use the lambda body in the thunk in make-thread just as well?
    (bt:with-lock-held (lock)
        (do-stuff)
        (do-more-stuff)
        (bt:condition-wait  cv lock))) ;; Should the call to condition-wait be within the body of with-lock-held or not?
Also, could someone please tell me where the output is going when I do this?

Code: Select all

(defvar *k0* nil)
(setf *k0* (bt:make-thread ;; capture returned thread object
                 (lambda()
                   (unwind-protect (loop) ;;
                     (print "Goodbye"))) :name "Say goodbye")) ;; With or without  the #.*standard-output*, I get no output... prints fine without a loop.

Hopefully it isn't a SBCL thing. Thanks.

Re: Bordeaux Threads Questions

Posted: Mon Jun 09, 2014 12:18 am
by jcbeaudoin
macrolyte wrote:A couple more questions (please see code below):

Code: Select all

;; is this a correct way for this?

(defun test() ;; Is the general pattern to use a defun? Couldn't you use the lambda body in the thunk in make-thread just as well?
    (bt:with-lock-held (lock)
        (do-stuff)
        (do-more-stuff)
        (bt:condition-wait  cv lock))) ;; Should the call to condition-wait be within the body of with-lock-held or not?
The order of bt:condition-wait and do-stuff seems to be reversed, one usually waits on a condition variable being signalled before doing something about it.
macrolyte wrote:Also, could someone please tell me where the output is going when I do this?

Code: Select all

(defvar *k0* nil)
(setf *k0* (bt:make-thread ;; capture returned thread object
                 (lambda()
                   (unwind-protect (loop) ;;
                     (print "Goodbye"))) :name "Say goodbye")) ;; With or without  the #.*standard-output*, I get no output... prints fine without a loop.

Hopefully it isn't a SBCL thing. Thanks.
The code as it stands here above does not produce any output, at least not yet. The form (loop) being the degenerate version of the simple variant of the cl:loop macro it denotes an infinitely busy empty loop that does nothing more than waste CPU cycles very quickly and for ever. (In C you could write it as "for (;;);" or as "while (1);")
So the thread you thus created spins without any output until something kicks it out of its merry looping. And that was the reason for the process-kill form wrapped around the original piece of code you took from "Lisp Outside the Box". In Allegro CL, AFAICT, the function process-kill when applied to a thread will interrupt its execution, force it to unwind its call stack and bring it to a state of terminal inactivity. It is during the unwinding of the thread's call stack that 'Goodbye' will end up printed on a stream.

In Bordeaux Threads you may try to use bt:destroy-thread on your *k0* thread to achieve the same result but beware that its specification clearly states that it is implementation-defined whether the thus targetted thread will have its call stack unwound or not. On SBCL it happens to be the case. On other implementations you may try to use (bt:interrupt-thread *k0* #'(lambda () (abort)) instead in order to force the thread's call stack unwinding that you expect.

But there is still that question of where is 'Goodbye' printed and, mainly, why there.