Bordeaux Threads Questions

Discussion of Common Lisp
macrolyte
Posts: 40
Joined: Sat Jan 25, 2014 8:56 pm
Location: The wilderness of North America

Bordeaux Threads Questions

Post by macrolyte » Thu Jun 05, 2014 6:05 pm

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.

pjstirling
Posts: 166
Joined: Sun Nov 28, 2010 4:21 pm

Re: Bordeaux Threads Questions

Post by pjstirling » Fri Jun 06, 2014 5:55 am

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.

pjstirling
Posts: 166
Joined: Sun Nov 28, 2010 4:21 pm

Re: Bordeaux Threads Questions

Post by pjstirling » Fri Jun 06, 2014 6:23 am

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

Goheeca
Posts: 271
Joined: Thu May 10, 2012 12:54 pm
Contact:

Re: Bordeaux Threads Questions

Post by Goheeca » Fri Jun 06, 2014 9:27 am

For the process-wait, seems to me, are condition variables more appropriate than a simple join-thread.
cl-2dsyntax is my attempt to create a Python-like reader. My mirror of CLHS (and the dark themed version). Temporary mirrors of aferomentioned: CLHS and a dark version.

macrolyte
Posts: 40
Joined: Sat Jan 25, 2014 8:56 pm
Location: The wilderness of North America

Re: Bordeaux Threads Questions

Post by macrolyte » Sat Jun 07, 2014 2:28 pm

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?

Goheeca
Posts: 271
Joined: Thu May 10, 2012 12:54 pm
Contact:

Re: Bordeaux Threads Questions

Post by Goheeca » Sat Jun 07, 2014 2:59 pm

Functions have own scope, it's the same thing like:

Code: Select all

(let ((place place))
  (setf place "done"))
cl-2dsyntax is my attempt to create a Python-like reader. My mirror of CLHS (and the dark themed version). Temporary mirrors of aferomentioned: CLHS and a dark version.

Goheeca
Posts: 271
Joined: Thu May 10, 2012 12:54 pm
Contact:

Re: Bordeaux Threads Questions

Post by Goheeca » Sat Jun 07, 2014 3:32 pm

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.
cl-2dsyntax is my attempt to create a Python-like reader. My mirror of CLHS (and the dark themed version). Temporary mirrors of aferomentioned: CLHS and a dark version.

macrolyte
Posts: 40
Joined: Sat Jan 25, 2014 8:56 pm
Location: The wilderness of North America

Re: Bordeaux Threads Questions

Post by macrolyte » Sat Jun 07, 2014 3:59 pm

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!

macrolyte
Posts: 40
Joined: Sat Jan 25, 2014 8:56 pm
Location: The wilderness of North America

Re: Bordeaux Threads Questions

Post by macrolyte » Sun Jun 08, 2014 4:10 pm

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.

jcbeaudoin
Posts: 2
Joined: Sat Nov 26, 2011 10:33 pm

Re: Bordeaux Threads Questions

Post by jcbeaudoin » Mon Jun 09, 2014 12:18 am

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.

Post Reply