doInParallel:

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

doInParallel:

Keith Alcock-3
Dolphin experts,

I would like to process the elements of an ordered collection in parallel.  After lots
of exploring the image, trail, and error, the following example solution emerged:

   | outerSemaphore innerSemaphore array |
   array := #(1 2 3 4 5) copy.
   outerSemaphore := Semaphore new
      primSetSignals: 1 - array size;
      yourself.
   innerSemaphore := Semaphore new.
   array do: [ :index |
      [
         | tmpIndex |
         tmpIndex := index.
         "(Delay forSeconds: 1) wait."
         innerSemaphore signal.
         "(Delay forSeconds: 1) wait."
         array at: tmpIndex put: tmpIndex + 1.
         outerSemaphore signal.
      ] fork.
      innerSemaphore wait.
   ].
   outerSemaphore wait.
   ^array.

The inner semaphore is used to protect access to the block variable.  The outer one is
to make the whole thing block until the entire array is processed.

There are a couple of problems with this.  If the second delay line is uncommented, the
answer is incorrect.  Waiting there allows the block to rerun and tmpIndex gets
overwritten before it is used.  The second problem is that I can't get a more general
solution to work when it is added to OrderedCollection or some of its superclasses so
that I can do

#(1 2 3) doInParallel: [ :each | each something ].

Besides, if no delays are allowed in the block, this isn't a very handy construction.

Both of these problems may have to do with the not quite block closures that Dolphin
5 is reported to have.  Does anyone know if that's the case?  Also, will this work
better in Dolphin 6: without the inner semaphore or with allowing delays inside the
block?  If someone has a better solution, please let me know.  Thanks.


Reply | Threaded
Open this post in threaded view
|

Re: doInParallel:

Blair McGlashan-2
Keith

You wrote in message news:[hidden email]...
> Dolphin experts,
>
> I would like to process the elements of an ordered collection in parallel.
After lots
> of exploring the image, trail, and error, the following example solution
emerged:
> ...[snip]...
> There are a couple of problems with this.  If the second delay line is
uncommented, the
> answer is incorrect.  Waiting there allows the block to rerun and tmpIndex
gets
> overwritten before it is used.  The second problem is that I can't get a
more general
> solution to work when it is added to OrderedCollection or some of its
superclasses so
> that I can do
>
> #(1 2 3) doInParallel: [ :each | each something ].
>
> Besides, if no delays are allowed in the block, this isn't a very handy
construction.
>
> Both of these problems may have to do with the not quite block closures
that Dolphin
> 5 is reported to have.  Does anyone know if that's the case?

Yes, you are certainly running into the limitations of Smalltalk-80 style
blocks there. Remember that all the temps in the blocks (including any block
arguments) are allocated in a shared slot in a context associated with the
method. Therefore if you create a sequence of blocks in a loop inside the
same method, then all will share the same temp slots. Consequently each of
the forked blocks is using the same slot for 'tmpIndex'.

A general pattern to overcome this issue is to extract the block creation
into a separate method invocation, i.e.
...
array do: [:index | (self innerBlock: index) fork]
...

You can't do this for a higher-order parallel enumerator, because you aren't
in control of the creation of the operation block. However a workaround is
to explicitly create a separate home context for each block, e.g. for your
original example:

    | counter array r |
    array := #(1 2 3 4 5) copy.
    counter := Semaphore new.
    r := Random new.
    array do:
      [:index |
      block :=
        [Processor sleep: r next * 1000.
        array at: index put: index + 1.
        counter signal].
      block outer: block outer copy.
      block fork].
    array size timesRepeat: [counter wait].
    ^array

If the block is passed in to a method, then you'll obviously have to copy
that too to create unique instances.

>...Also, will this work
> better in Dolphin 6: without the inner semaphore or with allowing delays
inside the
> block?

Yes, it will (does) work as expected in D6. You don't (in D6) need the
'tmpIndex' variable or 'innerSemaphore', since the 'index' argument is
copied into each of the forked blocks. Also the excessSignal count of a
Semaphore must be >= 0, not negative. Here's how I might write a general
parallel transform for D6:

collectInParallel: transformBlock
 | finished answer size |
 size := self size.
 answer := self copyLikeOfSize: size.
 finished := Semaphore new.
 self keysAndValuesDo:
   [:i :each |

   [answer at: i put: (transformBlock value: each).
   finished signal] fork].
 size timesRepeat: [finished wait].
 ^answer

>...If someone has a better solution, please let me know.  Thanks.

I've run out of time, so I must leave it as an exercise for the reader to
write a #collectInParallel: for D5.

Another way would might be to use DeferredValues.

Regards

Blair