Skip to content

Instantly share code, notes, and snippets.

@Dierk
Created August 28, 2013 18:48
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Dierk/6369753 to your computer and use it in GitHub Desktop.
Save Dierk/6369753 to your computer and use it in GitHub Desktop.
Massively parallel consistent ant moves
import groovy.transform.Immutable
import groovyx.gpars.dataflow.KanbanFlow
import groovyx.gpars.dataflow.KanbanLink
import groovyx.gpars.dataflow.KanbanTray
import groovyx.gpars.dataflow.ProcessingNode
import static groovyx.gpars.dataflow.ProcessingNode.node
/*
For a general introduction see https://gist.github.com/Dierk/6365780.
This system of ant moves is inspired by the excellent http://www.youtube.com/watch?v=dGVqrGmwOAw .
It does not cover all details of an ant colony simulation but only the core ant moves.
The rest (movement strategies, home, food, pheromons, evaporation, visuals) could be easily added
by extending the KanbanFlow chain.
This approach offers a solution where we neither need persistent datatypes nor software transactional memory (STM)
and certainly no locking. It is all exclusively modelled as KanbanFlow nodes.
Ants cannot look further than one cell in either direction and they cannot look into
the past or future to decide upon movements. So just like with the Game of Life example, the parallel system
works from individual cells that are connected to their direct neighbors.
The tricky part is to provide a consistent world view to the outside in a massively parallel system.
When ants move to a destination cell, they must appear in the destination cell and disappear from the origin.
At no observable time must they appear in both cells or in no cell.
Ants can also be blocked because there already is an ant in the destination cell.
There can be conflicts when two ants are heading for the same destination cell.
The combination of requirements above is usually seen as "the really hard stuff".
It appears to call for transactional support to rollback contentions and the big weaponry of parallel programming.
The solution below goes a different route. I has no shared "world" state (not even an immutable one) where
contentions can possibly occur.
It has a notion of "generations". Generations do not materialize as part of the calculation, though.
Only the observing printer creates the world view of a generation on the fly for reporting.
The idea of "transactions" is replaced with a strategy that roughly comparable to the
"2-phase commit protocol" (2PC), spanning over two generations:
- generation 1: every Ant knows where it is heading towards ("prepare" in 2PC)
- generation 1: every cell can decide where to pull the next ant candidate from ("ready" in 2PC)
- generation 2: when both have a rendezvous, the ant moves at once in this generation ("commit" in 2PC)
(I hope you can see the relation to settlements of a deal or money transfer between accounts.)
At no time is any cell operator changing any state - particularly not the state of neighboring cells!
He can only set his own cell state for the next generation and indicate his pull preference.
@author Dierk Koenig
*/
@Immutable class Ant {int headingX, headingY } // heading is in [-1,0,1]
ProcessingNode makeCellNode() {
def ProcessingNode thisNode = new ProcessingNode()
thisNode.body = {
publish,
inSelf, outSelf,
in1, out1, in2, out2, in3, out3, in4, out4, // in and out meaning depends on wiring sequence
in6, out6, in7, out7, in8, out8, in9, out9
->
def my = inSelf.take()
def result = [ // collecting result signal
generation: my.generation + 1, // in this generation
x:my.x, y:my.y, // this cell
ant: my.ant, // contains this ant (may be null)
sx:my.sx, // source x,y (relative) where the next ant will be pulled from. 0,0 == no pull
sy:my.sy // this is used as the rendezvous syncpoint
]
def all = [in1, out1, in2, out2, in3, out3, in4, out4, in6, out6, in7, out7, in8, out8, in9, out9]
def ins = all.findAll{ it.link.consumerSpec == thisNode }
def outs = all.findAll{ it.link.producerSpec == thisNode }
def signals = ins*.take()
assert signals*.generation.every { it == my.generation } // sanity check
def iAmPointingTo = null
if (my.ant != null){
iAmPointingTo = signals.find { it.x == wrap(my.x + my.ant.headingX) && it.y == wrap(my.y + my.ant.headingY) }
}
boolean destinationPulls = false
if (iAmPointingTo != null){
destinationPulls = (my.x == wrap(iAmPointingTo.x + iAmPointingTo.sx) && my.y == wrap(iAmPointingTo.y + iAmPointingTo.sy))
}
if (destinationPulls) {
result.ant = null // if there was an ant before, it is now elsewhere
}
def cellsWithAntsPointingToMe = signals.findAll { it.ant && wrap(it.x + it.ant.headingX) == my.x && wrap(it.y + it.ant.headingY) == my.y }
def myPullSource = signals.find { it.x == wrap(my.x + my.sx) && it.y == wrap(my.y + my.sy) }
// rendezvous
if (myPullSource != null && myPullSource in cellsWithAntsPointingToMe) {
result.ant = myPullSource.ant // keep the "ant" heading
result.sx = 0 // we will not pull ourselves the next turn but may be pulled
result.sy = 0
} else {
if (result.ant == null && cellsWithAntsPointingToMe) { // we are empty and a source is given, ready to pull
result.sx = - cellsWithAntsPointingToMe.first().ant.headingX
result.sy = - cellsWithAntsPointingToMe.first().ant.headingY
}
}
def outSignal = result.asImmutable()
publish << outSignal
outs.each { it << outSignal }
outSelf << outSignal
}
return thisNode
}
ProcessingNode makeObserver(KanbanLink funnel) {
return node { signal ->
funnel.downstream << new KanbanTray(link: funnel, product: signal.take())
}
}
ProcessingNode makePrinter() {
return node { inMap, outMap, inSignal, outSignal ->
def signal = inSignal.take()
def genMap = inMap.take()
def genNumber = signal.generation
def signalsInGeneration = genMap[genNumber]
signalsInGeneration << signal
if ( signalsInGeneration.size() == size * size ) {
genMap.remove genNumber
def board = makeBoard()
for (cell in signalsInGeneration) {
board[cell.y][cell.x] = cell.ant ? '*' : ' '
}
println "\ngeneration ${genNumber} :\n" +
board.join("\n") +
"\nfuture generations: " +
genMap.collect{ gen, signals -> "${gen}:${signals.size()}" }
}
outMap << genMap
~outSignal // outSignal will be triggered from the outside
}
}
int getSize() { 10 }
List<List<Integer>> makeBoard() { (0..9).collect { [null] * 10 } } // endless torus world
int wrap(int i) { (i + size) % size } // indexes wrap around in the torus world
startBoard = makeBoard()
[[1,0],[2,1],[0,2],[1,2],[2,2]].each { startBoard[it[0]][it[1]] = new Ant(0,1) } // glider
void seed(KanbanLink link, int x, int y) {
def tray = new KanbanTray(link: link, product: [x:x, y:y, generation: 0, ant: startBoard[x][y], sx: 0, sy:0])
link.downstream << tray
}
new KanbanFlow().with {
cycleAllowed = true
List<List<ProcessingNode>> cells = (0..<size).collect{ x -> (0..<size).collect { y -> makeCellNode() } }
def field = [
[-1, -1], [ 0, -1], [ 1, -1],
[-1, 0], [ 1, 0],
[-1, 1], [ 0, 1], [ 1, 1]
]
ProcessingNode printer = makePrinter()
KanbanLink mapLink = link printer to printer
mapLink.downstream << new KanbanTray(link:mapLink, product: [:].withDefault { [ ] } )
KanbanLink signalLink = link printer to printer
def currentLink
for (int x in 0..<size) {
for (int y in 0..<size) {
def center = cells[x][y]
currentLink = link center to makeObserver(signalLink)
seed currentLink, x, y
currentLink = link center to center
seed currentLink, x, y
}
}
for (int x in 0..<size) {
for (int y in 0..<size) {
def center = cells[x][y]
field.each {
def senderX = wrap(x + it[0])
def senderY = wrap(y + it[1])
def neighborCell = cells[senderX][senderY]
currentLink = link neighborCell to center
seed currentLink, senderX, senderY
}
}
}
start()
sleep 4000
stop()
}
@Dierk
Copy link
Author

Dierk commented Aug 28, 2013

and example output for ants moving downwards.

Note how the second ant is limbing behind at every second generation because the destination cell is occupied.
That is on purpose.

generation 1 :
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [2:87, 3:37, 4:10]

generation 2 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [3:93, 4:77, 5:51, 6:17]

generation 3 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [4:93, 5:69, 6:28, 7:2]

generation 4 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [5:81, 6:44, 7:9]

generation 5 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [6:96, 7:73, 8:15, 9:2]

generation 6 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [7:82, 8:22, 9:2]

generation 7 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [8:97, 9:76, 10:37]

generation 8 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [9:91, 10:75, 11:51, 12:15]

generation 9 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [10:88, 11:60, 12:21]

generation 10 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [11:91, 12:63, 13:16]

generation 11 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [12:92, 13:64, 14:13]

generation 12 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [13:76, 14:17]

generation 13 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [14:99, 15:95, 16:71, 17:31, 18:2]

generation 14 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
future generations: [15:95, 16:71, 17:31, 18:2]

generation 15 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
future generations: [16:96, 17:68, 18:19]

generation 16 :
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [17:95, 18:72, 19:16, 20:1]

generation 17 :
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
future generations: [18:96, 19:62, 20:20]

generation 18 :
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
future generations: [19:84, 20:54, 21:10]

generation 19 :
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [20:90, 21:59, 22:17, 23:2]

generation 20 :
[ , *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [21:91, 22:66, 23:12]

generation 21 :
[ , *,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  , *,  ,  ,  ,  ,  ,  ,  ]
[*, *, *,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
[ ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
future generations: [22:89, 23:56, 24:15]

Process finished with exit code 0

@Dierk
Copy link
Author

Dierk commented Aug 29, 2013

BTW: the code above uses a growing thread pool. You can restrict it to a pool of fixed size 10 by changing

new KanbanFlow().with {

to

new KanbanFlow().with {
    pooledGroup = new DefaultPGroup(new DefaultPool(true, 10))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment