Kevin Reid | 14 Feb 18:52 2008
Picon

Re: Bug with dotimes() and make-thread()

On Feb 14, 2008, at 12:30, obregonmateo <at> gmail.com wrote:

> However, I've found a BUG that shows up in the following code.  
> Sometimes this snippet generates an erroneous result:
>
> ;; first run:
>
> CL-USER> (let ((res nil))
> 	   (dotimes (n 3)
> 	     (push (sb-thread:make-thread #'(lambda () (cons n n))) res))
> 	   (mapcar #'sb-thread:join-thread (reverse res)))
>
> ((0 . 0) (2 . 2) (3 . 3))
...
> ((0 . 0) (1 . 1) (2 . 2))
...
> ((0 . 0) (1 . 1) (3 . 3))

This is not a bug, but permitted behavior of DOTIMES. According to  
the CLHS:

"It is implementation-dependent whether dotimes establishes a new  
binding of var on each iteration or whether it establishes a binding  
for var once at the beginning and then assigns it on any subsequent  
iterations."

SBCL currently does the latter. Therefore, your three lambdas close  
over the same binding, which is the one being mutated by DOTIMES.

The variation you are seeing comes from the OS scheduler: whether the  
thread retrieves n before or after DOTIMES proceeds to its next  
iteration.

You can also see this type of behavior without threads:

(let ((res nil))
   (dotimes (n 3)
     (push (lambda () (cons n n)) res))
   (mapcar #'funcall (reverse res)))

This will always return ((3 . 3) (3 . 3) (3 . 3)).

One way to do this correctly is:

(dotimes (n 3)
   (let ((n n))
     (push (sb-thread:make-thread #'(lambda () (cons n n))))

This establishes a new, never-reassigned, binding for each closure.

--

-- 
Kevin Reid                            <http://homepage.mac.com/kpreid/>

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

Gmane