concurrency gpars

103
© ASERT 2006-2013 Concurrency with GPars Dr Paul King @paulk_asert http:/slideshare.net/paulk_asert/ concurrency-gpars https://github.com/paulk-asert/ concurrency-gpars

Upload: paul-king

Post on 10-May-2015

16.432 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: concurrency gpars

© AS

ER

T 2006-2013

Concurrency with GPars

Dr Paul King@paulk_asert

http:/slideshare.net/paulk_asert/concurrency-gpars

https://github.com/paulk-asert/concurrency-gpars

Page 2: concurrency gpars

TopicsIntro• Useful Groovy features for Concurrency• GPars• Case Studies• More Info• Bonus Material

GPars - 2

© ASERT 2006-

2013

Page 3: concurrency gpars

"Andy giveth and Bill taketh away"

GPars - 3

Sou

rce:

Her

b S

utte

r: h

ttp:

//w

ww

.got

w.c

a/pu

blic

atio

ns/c

oncu

rren

cy-d

dj.h

tm

Page 4: concurrency gpars

Why is it hard?• Many issues to deal with:

– Doing things in parallel, concurrently, asynchronously• Processes, Threads, Co-routines, Events, Scheduling

– Sharing/Synchronization Mechanisms• shared memory, locks, transactions, wait/notify, STM,

message passing, actors, serializability, persistence, immutability

– Abstractions• Shared memory on top of messaging passing• Message passing on top of shared memory• Dataflow, Selective Communication, Continuations

– Data Structures and Algorithms• Queues, Heaps, Trees• Sorting, Graph Algorithms

GPars - 4

Page 5: concurrency gpars

Java Concurrency Best Practice?• Java Concurrency in Practice:

–“If multiple threads access thesame mutable state variablewithout appropriate synchronization,your program is broken”

–“When designing thread-safe classes, good object-oriented techniques – encapsulation, immutability, and clear specification of invariants – are your best friends”

GPars - 5

Page 6: concurrency gpars

Ralph Johnson: Parallel Programming

• Styles of parallel programming– Threads and locks

– Asynchronous messages e.g. Actors – no or limited shared memory

– Sharing with deterministic restrictions e.g. Fork-join

– Data parallelism

GPars - 6

© ASERT 2006-

2013

http://strangeloop2010.com/talk/presentation_file/14485/Johnson-DataParallelism.pdf

Increasin

g A

bstractio

n

Page 7: concurrency gpars

Ralph Johnson: Parallel Programming

• Styles of parallel programming– Threads and locks

• Nondeterministic, low-level, rumored humans can do this

– Asynchronous messages e.g. Actors –no or limited shared memory

• Nondeterministic, ok for I/O but be careful with side-effects

– Sharing with deterministic restrictionse.g. Fork-join

• Hopefully deterministic semantics, not designed for I/O

– Data parallelism• Deterministic semantics, easy, efficient, not designed for I/O

GPars - 7

© ASERT 2006-

2013

http://strangeloop2010.com/talk/presentation_file/14485/Johnson-DataParallelism.pdf

Each approach has some caveats

Page 8: concurrency gpars

GPars• http://gpars.codehaus.org/• Library classes and DSL sugar providing

intuitive ways for Groovy developers tohandle tasks concurrently. Logical parts:– Data Parallelism features use JSR-166y Parallel Arrays

to enable multi-threaded collection processing– Asynchronous functions extend the Java built-in support

for executor services to enable multi-threaded closure processing

– Dataflow Concurrency supports natural shared-memory concurrency model, using single-assignment variables

– Actors provide an implementation of Erlang/Scala-like actors including "remote" actors on other machines & CSP

– Safe Agents provide a non-blocking mt-safe reference to mutable state; inspired by "agents" in Clojure

GPars - 8

© ASERT 2006-

2013

Page 9: concurrency gpars

Choosing approaches

GPars - 9

For

mor

e de

tails

see

: ht

tp:/

/gpa

rs.c

odeh

aus.

org/

Con

cept

s+co

mpa

red

ParallelCollections

Data Parallelism

TaskParallelism

Streamed DataParallelism

Fork/Join

Dataflowoperators

CSP

Actors

Dataflow tasksActors

Asynch fun’sCSP

Fork/Join

ImmutableStm, Agents

Special collectionsSynchronization

Linear Recursive

Linear

Recursive

SharedData

IrregularRegular

Page 10: concurrency gpars

Coordination approaches

GPars - 10

Sou

rce:

ReG

inA

– G

roov

y in

Act

ion,

2nd

edi

tion

Data Parallelism:Fork/Join

Map/Reduce

Fixed coordination(for collections)

Actors Explicit coordination

Safe Agents Delegated coordination

Dataflow Implicit coordination

Page 11: concurrency gpars

Groovy concurrency value add

GPars - 11

JVM ConcurrencyThreads, synchronization, locks, semaphores, Phaser,

executor, fork-join, concurrent collections, atomic objects

GParsMap/reduce, fork/join,

asynchronous closures, actors, CSP, agents, dataflow concurrency

Your Application

GroovyThread & process

enhancements, immutability, laziness, @WithReadLock, @WithWriteLock, @Lazy,

@Synchronized

Page 12: concurrency gpars

Immutable Classes• Some Rules

– Don’t provide mutators– Ensure that no methods can

be overridden• Easiest to make the class final• Or use static factories & non-public

constructors

– Make all fields final– Make all fields private

• Avoid even public immutable constants

– Ensure exclusive access to any mutable components• Don’t leak internal references• Defensive copying in and out

– Optionally provide equals and hashCode methods– Optionally provide toString method

Page 13: concurrency gpars

@Immutable...• Java Immutable Class

– As per Joshua BlochEffective Java

© ASERT 2006-

2013

public final class Person { private final String first; private final String last;

public String getFirst() { return first; }

public String getLast() { return last; }

@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((last == null) ? 0 : last.hashCode()); return result; }

public Person(String first, String last) { this.first = first; this.last = last; } // ...

// ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (last == null) { if (other.last != null) return false; } else if (!last.equals(other.last)) return false; return true; }

@Override public String toString() { return "Person(first:" + first + ", last:" + last + ")"; }

}

Page 14: concurrency gpars

...@Immutable...• Java Immutable Class

– As per Joshua BlochEffective Java

© ASERT 2006-

2013

public final class Person { private final String first; private final String last;

public String getFirst() { return first; }

public String getLast() { return last; }

@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((last == null) ? 0 : last.hashCode()); return result; }

public Person(String first, String last) { this.first = first; this.last = last; } // ...

// ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (last == null) { if (other.last != null) return false; } else if (!last.equals(other.last)) return false; return true; }

@Override public String toString() { return "Person(first:" + first + ", last:" + last + ")"; }

}

boilerplate

Page 15: concurrency gpars

...@Immutable© ASERT 2006-

2013

@Immutable class Person { String first, last}

Page 16: concurrency gpars

Approaches to managing collection storage• Mutable • Persistent

© ASERT 2006-

2013

• Immutable

‘c’ ‘a’ ‘c’ ‘a’

Add ‘t’ Add ‘t’ Add ‘t’

‘c’ ‘a’ ‘t’‘c’ ‘a’

‘c’ ‘a’ ‘t’

X‘c’

‘a’

‘t’

‘c’

‘a’

ArrayList, HashSet, HashMap asImmutable(), Guava, mop tricks fj, clj-ds, pcollections, totallylazy

Page 17: concurrency gpars

Topics• Intro• Useful Groovy features for ConcurrencyGPars• Case Studies• More Info• Bonus Material

GPars - 17

© ASERT 2006-

2013

Page 18: concurrency gpars

Choosing approaches

GPars - 18

For

mor

e de

tails

see

: ht

tp:/

/gpa

rs.c

odeh

aus.

org/

Con

cept

s+co

mpa

red

ParallelCollections

Data Parallelism

TaskParallelism

Streamed DataParallelism

Fork/Join

Dataflowoperators

CSP

Actors

Dataflow tasksActors

Asynch fun’sCSP

Fork/Join

ImmutableStm, Agents

Special collectionsSynchronization

Linear Recursive

Linear

Recursive

SharedData

IrregularRegular

Page 19: concurrency gpars

Groovy Sequential Collection

GPars - 19

© ASERT 2006-

2013

def oneStarters = (1..30) .collect { it ** 2 } .findAll { it ==~ '1.*' } assert oneStarters == [1, 16, 100, 121, 144, 169, 196]

assert oneStarters.max() == 196 assert oneStarters.sum() == 747

Page 20: concurrency gpars

GPars Parallel Collections…

GPars - 20

© ASERT 2006-

2013

import static groovyx.gpars.GParsPool.withPool

withPool { def oneStarters = (1..30) .collectParallel { it ** 2 } .findAllParallel { it ==~ '1.*' } assert oneStarters == [1, 16, 100, 121, 144, 169, 196]

assert oneStarters.maxParallel() == 196 assert oneStarters.sumParallel() == 747}

Page 21: concurrency gpars

…GPars Parallel Collections

• Suitable when– Each iteration is independent, i.e. not:

fact[index] = index * fact[index - 1]– Iteration logic doesn’t use non-thread safe code– Size and indexing of iteration are important

GPars - 21

© ASERT 2006-

2013

import static groovyx.gpars.GParsPool.withPool

withPool { def oneStarters = (1..30) .collectParallel { it ** 2 } .findAllParallel { it ==~ '1.*' } assert oneStarters == [1, 16, 100, 121, 144, 169, 196]

assert oneStarters.maxParallel() == 196 assert oneStarters.sumParallel() == 747}

Page 22: concurrency gpars

Parallel Collection Variations• Apply some Groovy metaprogramming

GPars - 22

© ASERT 2006-

2013

import static groovyx.gpars.GParsPool.withPool

withPool { def oneStarters = (1..30).makeConcurrent() .collect { it ** 2 } .findAll { it ==~ '1.*' } .findAll { it ==~ '...' } assert oneStarters == [100, 121, 144, 169, 196]}

import groovyx.gpars.ParallelEnhancer

def nums = 1..5ParallelEnhancer.enhanceInstance(nums)assert [1, 4, 9, 16, 25] == nums.collectParallel{ it * it }

Page 23: concurrency gpars

GPars parallel methods for collectionsTransparent Transitive? Parallel Lazy?

any { ... } anyParallel { ... } yes

collect { ... } yes collectParallel { ... }

count(filter) countParallel(filter)

each { ... } eachParallel { ... }eachWithIndex { ...

} eachWithIndexParallel

{ ... }every { ... } everyParallel { ... } yes

find { ... } findParallel { ... }

findAll { ... } yes findAllParallel { ... }

findAny { ... } findAnyParallel { ... }

fold { ... } foldParallel { ... }fold(seed) { ... }

foldParallel(seed) { ... }

grep(filter) yes grepParallel(filter)

groupBy { ... } groupByParallel { ... }

max { ... } maxParallel { ... }

max() maxParallel()

min { ... } minParallel { ... }

min() minParallel()

split { ... } yes splitParallel { ... }

sum sumParallel // foldParallel +

GPars - 23Transitive means result is automatically transparent; Lazy means fails fast

For

mor

e de

tails

see

ReG

inA

or

the

GP

ars

docu

men

tatio

n

Page 24: concurrency gpars

GPars: Map-Reduce

GPars - 24

© ASERT 2006-

2013

import static groovyx.gpars.GParsPool.withPool

withPool { def oneStarters = (1..30).parallel .map { it ** 2 } .filter { it ==~ '1.*' }

assert oneStarters.collection == [1, 16, 100, 121, 144, 169, 196]

// aggregations/reductions assert oneStarters.max() == 196 assert oneStarters.reduce { a, b -> a + b } == 747 assert oneStarters.sum() == 747}

Page 25: concurrency gpars

GPars parallel array methods

Method Return Typecombine(initValue) { ... } Mapfilter { ... } Parallel arraycollection CollectiongroupBy { ... } Mapmap { ... } Parallel arraymax() Tmax { ... } Tmin() Tmin { ... } Treduce { ... } Treduce(seed) { ... } Tsize() intsort { ... } Parallel arraysum() Tparallel // on a Collection Parallel array

GPars - 25

For

mor

e de

tails

see

ReG

inA

or

the

GP

ars

docu

men

tatio

n

Page 26: concurrency gpars

Parallel Collections vs Map-Reduce

GPars - 26

Fork Fork

JoinJoin

Map

Map

ReduceMap

Map

Reduce

Reduce

Map

Filter

Filter Map

Page 27: concurrency gpars

Concurrency challenge…• Suppose we have the following

calculation involving several functions:

• And we want to use our available cores …

GPars - 27

© ASERT 2006-

2013

// example adapted from Parallel Programming with .Netdef (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 + [{ x, y -> x + y }]

def a = 5

def b = f1(a)def c = f2(a)def d = f3(c)def f = f4(b, d)

assert f == 10

Page 28: concurrency gpars

…Concurrency challenge…• We can analyse the example’s task graph:

GPars - 28

© ASERT 2006-

2013

// example adapted from Parallel Programming with .Netdef (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 + [{ x, y -> x + y }]

def a = 5

def b = f1(a)def c = f2(a)def d = f3(c)def f = f4(b, d)

assert f == 10

f2

f3

f1

f4

aa

b

c

d

f

Page 29: concurrency gpars

…Concurrency challenge…• Manually using asynchronous functions:

GPars - 29

© ASERT 2006-

2013

// example adapted from Parallel Programming with .Netdef (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 + [{ x, y -> x + y }]

import static groovyx.gpars.GParsPool.withPool

withPool(2) { def a = 5

def futureB = f1.callAsync(a) def c = f2(a) def d = f3(c) def f = f4(futureB.get(), d)

assert f == 10}

f2

f3

f1

f4

aa

b

c

d

f

Page 30: concurrency gpars

…Concurrency challenge• And with GPars Dataflows:

GPars - 30

© ASERT 2006-

2013

def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 + [{ x, y -> x + y }]

import groovyx.gpars.dataflow.Dataflowsimport static groovyx.gpars.dataflow.Dataflow.task

new Dataflows().with { task { a = 5 } task { b = f1(a) } task { c = f2(a) } task { d = f3(c) } task { f = f4(b, d) } assert f == 10}

f2

f3

f1

f4

aa

b

c

d

f

Page 31: concurrency gpars

…Concurrency challenge• And with GPars Dataflows:

GPars - 31

© ASERT 2006-

2013

def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 + [{ x, y -> x + y }]

import groovyx.gpars.dataflow.Dataflowsimport static groovyx.gpars.dataflow.Dataflow.task

new Dataflows().with { task { f = f4(b, d) } task { d = f3(c) } task { c = f2(a) } task { b = f1(a) } task { a = 5 } assert f == 10}

f2

f3

f1

f4

aa

b

c

d

f

Page 32: concurrency gpars

GPars: Dataflows...

GPars - 32

© ASERT 2006-

2013 import groovyx.gpars.dataflow.DataFlows

import static groovyx.gpars.dataflow.DataFlow.task

final flow = new DataFlows()task { flow.result = flow.x + flow.y }task { flow.x = 10 }task { flow.y = 5 }assert 15 == flow.result

new DataFlows().with { task { result = x * y } task { x = 10 } task { y = 5 } assert 50 == result}

510

yx

*

Page 33: concurrency gpars

...GPars: Dataflows...• Evaluating:

GPars - 33

© ASERT 2006-

2013

import groovyx.gpars.dataflow.DataFlowsimport static groovyx.gpars.dataflow.DataFlow.task

final flow = new DataFlows()task { flow.a = 10 }task { flow.b = 5 }task { flow.x = flow.a - flow.b }task { flow.y = flow.a + flow.b }task { flow.result = flow.x * flow.y }assert flow.result == 75

b

10 5

a

+-

*

result = (a – b) * (a + b)

x y

Question: what happens if I change the order of the task statements here?

Page 34: concurrency gpars

...GPars: Dataflows...• Naive attempt for loops

GPars - 34

© ASERT 2006-

2013 import groovyx.gpars.dataflow.Dataflowsimport static groovyx.gpars.dataflow.Dataflow.task

final flow = new Dataflows()[10, 20].each { thisA -> [4, 5].each { thisB -> task { flow.a = thisA } task { flow.b = thisB } task { flow.x = flow.a - flow.b } task { flow.y = flow.a + flow.b } task { flow.result = flow.x * flow.y } println flow.result }}// => java.lang.IllegalStateException: A DataflowVariable can only be assigned once.

... task { flow.a = 10 } ... task { flow.a = 20 }

Don’t do this!

X

Page 35: concurrency gpars

...GPars: Dataflows...

GPars - 35

© ASERT 2006-

2013

import groovyx.gpars.dataflow.DataflowStreamimport static groovyx.gpars.dataflow.Dataflow.*

final streamA = new DataflowStream()final streamB = new DataflowStream()final streamX = new DataflowStream()final streamY = new DataflowStream()final results = new DataflowStream()

operator(inputs: [streamA, streamB], outputs: [streamX, streamY]) { a, b -> streamX << a - b; streamY << a + b}operator(inputs: [streamX, streamY], outputs: [results]) { x, y -> results << x * y }

[[10, 20], [4, 5]].combinations().each{ thisA, thisB -> task { streamA << thisA } task { streamB << thisB }}4.times { println results.val }

b

10102020

4545

a

+-

*

8475

384375

Page 36: concurrency gpars

...GPars: Dataflows• Suitable when:

– Your algorithms can be expressed as mutually-independent logical tasks

• Properties:– Inherently safe and robust (no race conditions or

livelocks)– Amenable to static analysis– Deadlocks “typically” become repeatable– “Beautiful” (declarative) code

GPars - 36

© ASERT 2006-

2013

import groovyx.gpars.dataflow.Dataflowsimport static groovyx.gpars.dataflow.Dataflow.task

final flow = new Dataflows()task { flow.x = flow.y }task { flow.y = flow.x }

Page 37: concurrency gpars

GPars: Dataflow Sieve

GPars - 37

© ASERT 2006-

2013

final int requestedPrimeNumberCount = 1000final DataflowStream initialChannel = new DataflowStream()

task { (2..10000).each { initialChannel << it }}

def filter(inChannel, int prime) { def outChannel = new DataflowStream() operator([inputs: [inChannel], outputs: [outChannel]]) { if (it % prime != 0) { bindOutput it } } return outChannel}

def currentOutput = initialChannelrequestedPrimeNumberCount.times { int prime = currentOutput.val println "Found: $prime" currentOutput = filter(currentOutput, prime)}

Source: http://groovyconsole.appspot.com/script/235002

Page 38: concurrency gpars

GPars: Actors...• Actors provide explicit coordination: they

don’t share state, instead coordinating via asynchronous messages– Contrasting with predefined coordination for fork/join &

map/filter/reduce & implicit coordination for dataflow– Messages are processed one at a time normally in the

order they were sent (which is non-deterministic due to asynchronous nature)

– Some actor systems allowing message delivery to be prioritised; others allow for sharing some (readonly) state; some allow remote actors for load balancing/robustness

• Not new in concept– But has received recent publicity due to special

support in Erlang, Scala and other languages GPars - 38

© ASERT 2006-

2013

Page 39: concurrency gpars

…GPars: Actors...• Class with the following lifecycle &

methods– But also DSL sugar & enhancements

GPars - 39

© ASERT 2006-

2013

start()stop()act()send(msg)sendAndWait(msg)loop { }react { msg -> }msg.reply(replyMsg)receive()join()

Page 40: concurrency gpars

…GPars: Actors...

GPars - 40

© ASERT 2006-

2013

import groovyx.gpars.actor.DynamicDispatchActor

class VotingActor extends DynamicDispatchActor { void onMessage(String language) { processVote(language) } void onMessage(List languages) { languages.each{ processVote it } } private processVote(language) { if (language.startsWith('G')) println "You voted for $language" else println 'Sorry, please try again' }}

final votes = new VotingActor().start()votes << 'Groovy'votes << 'C++'votes << ['Groovy', 'Go', 'Dart']votes.stop()votes.join()

You voted for GroovySorry, please try againYou voted for GroovyYou voted for GoSorry, please try again

Page 41: concurrency gpars

…GPars: Actors...

GPars - 41

© ASERT 2006-

2013

import static groovyx.gpars.actor.Actors.*

def votes = reactor { it.endsWith('y') ? "You voted for $it" : "Sorry, please try again"}

println votes.sendAndWait('Groovy')println votes.sendAndWait('JRuby')println votes.sendAndWait('Go')

def languages = ['Groovy', 'Dart', 'C++']def booth = actor { languages.each{ votes << it } loop { languages.size().times { react { println it } } stop() }}

booth.join(); votes.stop(); votes.join()

You voted for GroovyYou voted for JRubySorry, please try againYou voted for GroovySorry, please try againSorry, please try again

Page 42: concurrency gpars

…GPars: Actors

GPars - 42

© ASERT 2006-

2013

import groovyx.gpars.activeobject.*

@ActiveObjectclass VotingActiveObject { @ActiveMethod vote(String language) { processVote(language) } @ActiveMethod vote(List<String> languages) { languages.collect{ processVote it } } private processVote(language) { if (language.size() == 6) "You voted for $language" else 'Sorry, please try again' }}

def voter = new VotingActiveObject()def result1 = voter.vote('Scala')def result2 = voter.vote('Groovy')def result3 = voter.vote(['Pascal', 'Clojure', 'Groovy'])[result1.get(), result2.get(), *result3.get()].each{ println it }

Sorry, please try againYou voted for GroovyYou voted for PascalSorry, please try againYou voted for Groovy

Page 43: concurrency gpars

Agents...• Agents safeguard non-thread safe objects• Only the agent can update the underlying

object• “Code” to update the protected object is

sent to the agent• Can be used with other approaches

GPars - 43

© ASERT 2006-

2013

Page 44: concurrency gpars

…Agents…

GPars - 44

© ASERT 2006-

2013

def random = new Random()def randomDelay = { sleep random.nextInt(10) }String result = ''('a'..'z').each { letter -> Thread.start{ randomDelay() result += letter }}sleep 100 // poor man's join

println resultprintln result.size()

Unsafe!

Page 45: concurrency gpars

…Agents…

GPars - 45

© ASERT 2006-

2013

import groovyx.gpars.agent.Agent

def random = new Random()def randomDelay = { sleep random.nextInt(10) }def agent = new Agent<String>('')('a'..'z').each { letter -> Thread.start{ randomDelay() agent.send{ updateValue it + letter } }}sleep 100 // poor man's joinString result = agent.valprintln resultprintln result.size()

Page 46: concurrency gpars

…Agents

GPars - 46

© ASERT 2006-

2013

import groovyx.gpars.agent.Agent

def random = new Random()def randomDelay = { sleep random.nextInt(10) }def agent = new Agent<String>('')def threads = ('a'..'z').collect { letter -> Thread.start { randomDelay() agent.send{ updateValue it << letter } }}threads*.join()String result = agent.valprintln resultprintln result.size()

Page 47: concurrency gpars

Software Transactional Memory…

GPars - 47

© ASERT 2006-

2013

@Grab('org.multiverse:multiverse-beta:0.7-RC-1')import org.multiverse.api.references.LongRefimport static groovyx.gpars.stm.GParsStm.atomicimport static org.multiverse.api.StmUtils.newLongRef

class Account { private final LongRef balance

Account(long initial) { balance = newLongRef(initial) }

void setBalance(long newBalance) { if (newBalance < 0) throw new RuntimeException("not enough money") balance.set newBalance }

long getBalance() { balance.get() }}// ...

Page 48: concurrency gpars

…Software Transactional Memory

GPars - 48

© ASERT 2006-

2013

// ...def from = new Account(20)def to = new Account(20)def amount = 10

def watcher = Thread.start { 15.times { atomic { println "from: ${from.balance}, to: ${to.balance}" } sleep 100 }}sleep 150try { atomic { from.balance -= amount to.balance += amount sleep 500 } println 'transfer success'} catch(all) { println all.message}atomic { println "from: $from.balance, to: $to.balance" }

watcher.join()

Page 49: concurrency gpars

Topics• Intro• Useful Groovy features for Concurrency• GParsCase Studies

Web TestingWord Split

• More Info• Bonus Material

GPars - 49

© ASERT 2006-

2013

Page 50: concurrency gpars

GPars for testing

GPars - 50

© ASERT 2006-

2013

@Grab('net.sourceforge.htmlunit:htmlunit:2.6')import com.gargoylesoftware.htmlunit.WebClient@Grab('org.codehaus.gpars:gpars:0.10')import static groovyx.gpars.GParsPool.*

def testCases = [ ['Home', 'Bart', 'Content 1'], ['Work', 'Homer', 'Content 2'], ['Travel', 'Marge', 'Content 3'], ['Food', 'Lisa', 'Content 4']]

withPool(3) { testCases.eachParallel{ category, author, content -> postAndCheck category, author, content }}

private postAndCheck(category, author, content) { ...

Page 51: concurrency gpars

Topics• Intro• Useful Groovy features for Concurrency• GParsCase Studies

Web TestingWord Split

• More Info• Bonus Material

GPars - 51

© ASERT 2006-

2013

Page 52: concurrency gpars

Word Split with Fortress

GPars - 52

© ASERT 2006-

2013

Guy Steele’s StrangeLoop keynote (from slide 52 onwards for several slides):http://strangeloop2010.com/talk/presentation_file/14299/GuySteele-parallel.pdf

Page 53: concurrency gpars

Word Split…

GPars - 53

© ASERT 2006-

2013

def swords = { s -> def result = [] def word = '' s.each{ ch -> if (ch == ' ') { if (word) result += word word = '' } else word += ch } if (word) result += word result}

assert swords("This is a sample") == ['This', 'is', 'a', 'sample']

assert swords("Here is a sesquipedalian string of words") == ['Here', 'is', 'a', 'sesquipedalian', 'string', 'of', 'words']

Page 54: concurrency gpars

Word Split…

GPars - 54

© ASERT 2006-

2013

def swords = { s -> def result = [] def word = '' s.each{ ch -> if (ch == ' ') { if (word) result += word word = '' } else word += ch } if (word) result += word result}

Page 55: concurrency gpars

Word Split…

GPars - 55

© ASERT 2006-

2013

def swords = { s -> def result = [] def word = '' s.each{ ch -> if (ch == ' ') { if (word) result += word word = '' } else word += ch } if (word) result += word result}

Page 56: concurrency gpars

…Word Split…

GPars - 56

© ASERT 2006-

2013

Page 57: concurrency gpars

…Word Split…

GPars - 57

© ASERT 2006-

2013

Page 58: concurrency gpars

Segment(left1, m1, right1) Segment(left2, m2, right2)

Segment(left1, m1 + [ ? ] + m2, right2)

…Word Split…

GPars - 58

© ASERT 2006-

2013

Page 59: concurrency gpars

…Word Split…

GPars - 59

© ASERT 2006-

2013class Util { static maybeWord(s) { s ? [s] : [] } }import static Util.*

@Immutable class Chunk { String s public static final ZERO = new Chunk('') def plus(Chunk other) { new Chunk(s + other.s) } def plus(Segment other) { new Segment(s + other.l, other.m, other.r) } def flatten() { maybeWord(s) }}

@Immutable class Segment { String l; List m; String r public static final ZERO = new Segment('', [], '') def plus(Chunk other) { new Segment(l, m, r + other.s) } def plus(Segment other) { new Segment(l, m + maybeWord(r + other.l) + other.m, other.r) } def flatten() { maybeWord(l) + m + maybeWord(r) }}

Page 60: concurrency gpars

…Word Split…

GPars - 60

© ASERT 2006-

2013

def processChar(ch) { ch == ' ' ? new Segment('', [], '') : new Chunk(ch) }

def swords(s) { s.inject(Chunk.ZERO) { result, ch -> result + processChar(ch) } }

assert swords("Here is a sesquipedalian string of words").flatten() == ['Here', 'is', 'a', 'sesquipedalian', 'string', 'of', 'words']

Page 61: concurrency gpars

…Word Split…

GPars - 61

© ASERT 2006-

2013

Page 62: concurrency gpars

…Word Split…

GPars - 62

© ASERT 2006-

2013

Page 63: concurrency gpars

…Word Split…

GPars - 63

© ASERT 2006-

2013THREADS = 4def pwords(s) { int n = (s.size() + THREADS - 1) / THREADS def map = new ConcurrentHashMap() (0..<THREADS).collect { i -> Thread.start { def (min, max) = [ [s.size(), i * n].min(), [s.size(), (i + 1) * n].min() ] map[i] = swords(s[min..<max]) } }*.join() (0..<THREADS).collect { i -> map[i] }.sum().flatten()}

Page 64: concurrency gpars

…Word Split…

GPars - 64

© ASERT 2006-

2013

import static groovyx.gpars.GParsPool.withPool THRESHHOLD = 10 def partition(piece) { piece.size() <= THRESHHOLD ? piece : [piece[0..<THRESHHOLD]] + partition(piece.substring(THRESHHOLD))} def pwords = { input -> withPool(THREADS) { partition(input).parallel.map(swords).reduce{ a, b -> a + b }.flatten() }}

Page 65: concurrency gpars

…Guy Steele example in Groovy…

GPars - 65

© ASERT 2006-

2013def words = { s -> int n = (s.size() + THREADS - 1) / THREADS def min = (0..<THREADS).collectEntries{ [it, [s.size(),it*n].min()] } def max = (0..<THREADS).collectEntries{ [it, [s.size(),(it+1)*n].min()] } def result = new DataFlows().with { task { a = swords(s[min[0]..<max[0]]) } task { b = swords(s[min[1]..<max[1]]) } task { c = swords(s[min[2]..<max[2]]) } task { d = swords(s[min[3]..<max[3]]) } task { sum1 = a + b } task { sum2 = c + d } task { sum = sum1 + sum2 } println 'Tasks ahoy!' sum } switch(result) { case Chunk: return maybeWord(result.s) case Segment: return result.with{ maybeWord(l) + m + maybeWord(r) } }}

DataFlow version: partially hard-coded to 4 partitions for easier reading

Page 66: concurrency gpars

…Guy Steele example in Groovy…

GPars - 66

© ASERT 2006-

2013GRANULARITY_THRESHHOLD = 10THREADS = 4

println GParsPool.withPool(THREADS) { def result = runForkJoin(0, input.size(), input){ first, last, s -> def size = last - first if (size <= GRANULARITY_THRESHHOLD) { swords(s[first..<last]) } else { // divide and conquer def mid = first + ((last - first) >> 1) forkOffChild(first, mid, s) forkOffChild(mid, last, s) childrenResults.sum() } } switch(result) { case Chunk: return maybeWord(result.s) case Segment: return result.with{ maybeWord(l) + m + maybeWord(r) } }}

Fork/Join version

Page 67: concurrency gpars

…Guy Steele example in Groovy

GPars - 67

© ASERT 2006-

2013

println GParsPool.withPool(THREADS) { def ans = input.collectParallel{ processChar(it) }.sum() switch(ans) { case Chunk: return maybeWord(ans.s) case Segment: return ans.with{ maybeWord(l) + m + maybeWord(r) } }}

Just leveraging the algorithm’s parallel nature

Page 68: concurrency gpars

Topics• Intro• Useful Groovy features for Concurrency• Gpars• Case StudiesMore Info• Bonus Material

GPars - 68

© ASERT 2006-

2013

Page 69: concurrency gpars

More Information about Concurrency• Web sites

– http://gpars.codehaus.org/– http://g.oswego.edu/

Doug Lea's home page– http://gee.cs.oswego.edu/dl/concurrency-interest/– http://jcip.net/

Companion site for Java Concurrency in Practice– http://www.eecs.usma.edu/webs/people/okasaki/pubs.html#cup98

Purely Functional Data Structures– http://delicious.com/kragen/concurrency

Concurrency bookmark list– http://www.gotw.ca/publications/concurrency-ddj.htm

The Free Lunch is Over, Herb Sutter– http://manticore.cs.uchicago.edu/papers/damp07.pdf– http://mitpress.mit.edu/catalog/item/default.asp?ttype=2&tid=10142

Concepts, Techniques, and Models of Computer Programming

GPars - 69

Page 70: concurrency gpars

More Information about Groovy• Web sites

– http://groovy.codehaus.org– http://grails.codehaus.org– http://pleac.sourceforge.net/pleac_groovy (many examples)– http://www.asert.com.au/training/java/GV110.htm (workshop)

• Mailing list for users– [email protected]

• Information portals– http://www.aboutgroovy.org– http://www.groovyblogs.org

• Documentation (1000+ pages)– Getting Started Guide, User Guide, Developer Guide, Testing

Guide, Cookbook Examples, Advanced Usage Guide

• Books– Several to choose from ...

GPars - 70

Page 71: concurrency gpars

More Information: Groovy in Action

GPars - 71

Contains a chapter on

GPars!

Page 72: concurrency gpars

Bonus Material• Other concurrency options• Dining Philosopher Case Study

GPars - 72

© ASERT 2006-

2013

Page 73: concurrency gpars

Bonus Material• Other concurrency options

– Jetlang– JPPF– Multiverse– Gruple– Cascading– GridGain– ConTest

GPars - 73

© ASERT 2006-

2013

Page 74: concurrency gpars

Lightweight threads: Jetlang• Jetlang

– A high performance threading library– http://code.google.com/p/jetlang/

GPars - 74

import org.jetlang.fibers.ThreadFiberimport org.jetlang.channels.MemoryRequestChannelimport org.jetlang.channels.AsyncRequest

def req = new ThreadFiber() // or pooldef reply = new ThreadFiber()def channel = new MemoryRequestChannel()req.start()reply.start()

channel.subscribe(reply) { it.reply(it.request.sum()) }AsyncRequest.withOneReply(req, channel, [3, 4, 5]) { println it }

sleep 1000req.dispose()reply.dispose()

12

Page 75: concurrency gpars

Other High-Level Libraries: JPPF– Open source Grid Computing platform– http://www.jppf.org/

GPars - 75

import org.jppf.client.*import java.util.concurrent.Callable

class Task implements Callable, Serializable { private static final long serialVersionUID = 1162L public Object call() { println 'Executing Groovy' "Hello JPPF from Groovy" }}def client = new JPPFClient()def job = new JPPFJob()def task = new Task()job.addTask taskdef results = client.submit(job)for (t in results) { if (t.exception) throw t.exception println "Result: " + t.result}

Page 76: concurrency gpars

Other High-Level Libraries: Gruple...– http://code.google.com/p/gruple– Simple abstraction to coordinate and synchronize

threads with ease – based on Tuplespaces• Tuplespaces provide the illusion of a shared memory on top

of a message passing system, along with a small set of operations to greatly simplify parallel programming

– Example Tuple:[fname:"Vanessa", lname:"Williams", project:"Gruple"]

– Basic operations within a Tuplespace are:• put - insert a tuple into the space• get - read a tuple from the space (non-destructively)• take - take a tuple from the space (a destructive read)

– Further reading: Eric Freeman, Susanne Hupfer, and Ken Arnold. JavaSpaces Principles, Patterns, and Practice, Addison Wesley, 1999

GPars - 76

Page 77: concurrency gpars

…Other High-Level Libraries: Gruple...

GPars - 77

import org.gruple.SpaceService

def defaultSpace = SpaceService.getSpace()defaultSpace << [fname:"Vanessa", lname:"Williams", project:"Gruple"]println defaultSpace.get(fname:"Vanessa", lname:"Williams", project:"Gruple")

[project:Gruple, lname:Williams, fname:Vanessa]

Page 78: concurrency gpars

Other High-Level Libraries: ...Gruple...– Mandelbrot example (included in Gruple download)

GPars - 78

...Space space = SpaceService.getSpace("mandelbrot")

Map template = createTaskTemplate()Map task

String threadName = Thread.currentThread().namewhile(true) { ArrayList points task = space.take(template) println "Worker $threadName got task ${task['start']} for job ${task['jobId']}" points = calculateMandelbrot(task) Map result = createResult(task['jobId'], task['start'], points)

println "Worker $threadName writing result for task ${result['start']} for job ${result['jobId']}" space.put(result)}...

Page 79: concurrency gpars

Other High-Level Libraries: ...Gruple

GPars - 79

Page 80: concurrency gpars

Other High-Level Libraries: Cascading.groovy– API/DSL for executing tasks on a Hadoop cluster– http://www.cascading.org/

GPars - 80

def assembly = builder.assembly(name: "wordcount") { eachTuple(args: ["line"], results: ["word"]) { regexSplitGenerator(declared: ["word"], pattern: /[.,]*\s+/) } group(["word"]) everyGroup(args: ["word"], results: ["word", "count"]) { count() } group(["count"], reverse: true)}def map = builder.map() { source(name: "wordcount") { hfs(input) { text(["line"]) } } sink(name: "wordcount") { hfs(output) { text() } }}def flow = builder.flow(name: "wordcount", map: map, assembly: assembly)

Page 81: concurrency gpars

Other High-Level Libraries: GridGain…– Simple & productive to use grid computing platform– http://www.gridgain.com/

GPars - 81

class GridHelloWorldGroovyTask extends GridTaskSplitAdapter<String, Integer> { Collection split(int gridSize, Object phrase) throws GridException { // ... } Object reduce(List results) throws GridException { // ... }} import static GridFactory.*

start()def grid = getGrid()def future = grid.execute(GridHelloWorldGroovyTask, "Hello World")def phraseLen = future.get()stop(true)

Page 82: concurrency gpars

…Other High-Level Libraries: GridGain• http://gridgain.blogspot.com/2010/10/worlds-shortest-mapreduce-

app.html

GPars - 82

words = "Counting Letters In This Phrase".split(' ')map = new C1() { def apply(word) { word.size() } }reduce = sumIntReducer()println grid.forkjoin(SPREAD, yield(words, map), reduce)// => 27

grid.forkjoin(SPREAD,yield("Counting Letters In This Phrase".split(' '), new C1(){def apply(w){w.size()}}),sumReducer())

Page 83: concurrency gpars

Testing multi-threaded applications: ConTest...• Advanced Testing for Multi-Threaded Applications

– Tool for testing, debugging, and coverage-measuring of concurrent programs (collects runtime statistics)

– Systematically and transparently (using a java agent) schedules the execution of program threads in ways likely to reveal race conditions, deadlocks, and other intermittent bugs (collectively called synchronization problems) with higher than normal frequency

– The ConTest run-time engine adds heuristically controlled conditional instructions (adjustable by a preferences file) that force thread switches, thus helping to reveal concurrent bugs. You can use existing tests and run ConTest multiple times – by default different heuristics used each time it is run

• http://www.alphaworks.ibm.com/tech/contest

GPars - 83

Page 84: concurrency gpars

...Testing multi-threaded applications: ConTest

GPars - 84

NUM = 5count = 0def incThread = { n -> Thread.start{ sleep n*10 //synchronized(ParalInc) { count++ //}} }def threads = (1..NUM).collect(incThread)threads.each{ it.join() }assert count == NUM

targetClasses = ParalInctimeoutTampering = truenoiseFrequency = 500strength = 10000

Exception in thread "main" Assertion failed:

assert count == NUM | | | 4 | 5 false

> groovyc ParalInc.groovy> java -javaagent:../../Lib/ConTest.jar -cp %GROOVY_JAR%;. ParalInc

ParalInc.groovy

Page 85: concurrency gpars

Bonus Material• Dining Philosopher’s Case Study

– GPars Actors– GPars CSP– Multiverse– Jetlang– Gruple

GPars - 85

© ASERT 2006-

2013

Page 86: concurrency gpars

Dining Philosophers…

GPars - 86

PhilosopherThinking | Eating

PhilosopherThinking | Eating

PhilosopherThinking | Eating

PhilosopherThinking | Eating

PhilosopherThinking | Eating

ForkAvailable | InUse

ForkAvailable | InUse

ForkAvailable | InUse

ForkAvailable | InUse

ForkAvailable | InUse

Page 87: concurrency gpars

…Dining Philosophers

GPars - 87

PhilosopherThinking | Eating

PhilosopherThinking | Eating

PhilosopherThinking | Eating

PhilosopherThinking | Eating

PhilosopherThinking | Eating

ForkAvailable | InUse

ForkAvailable | InUse

ForkAvailable | InUse

ForkAvailable | InUse

ForkAvailable | InUse

Accepted | Rejected

Take | Release Take | Release

Page 88: concurrency gpars

Dining Philosophers: Actors...

GPars - 88

© ASERT 2006-

2013

// adapted from GPars example, repo: http://git.codehaus.org/gitweb.cgi?p=gpars.git// file: src/test/groovy/groovyx/gpars/samples/actors/DemoDiningPhilosophers.groovy@Grab('org.codehaus.gpars:gpars:0.10')import groovyx.gpars.actor.*import groovy.beans.Bindable

def names = ['socrates', 'plato', 'aristotle', 'descartes', 'nietzsche']Actors.defaultActorPGroup.resize names.size()

class Philosopher extends AbstractPooledActor { private random = new Random() String name int timesEaten = 0 def forks @Bindable String status

void act() { assert 2 == forks.size() loop { think() forks*.send new Take() react {a -> react {b -> if ([a, b].any {Rejected.isCase it}) { [a, b].find {Accepted.isCase it}?.reply new Release() } else { eat() [a, b]*.reply new Release() } } } } }

Page 89: concurrency gpars

…Dining Philosophers: Actors...

GPars - 89

© ASERT 2006-

2013

… void think() { setStatus('thinking') sleep random.nextInt(5000) setStatus('') }

void eat() { setStatus("eating ${++timesEaten}") sleep random.nextInt(3000) setStatus('') }

String toString() { switch (timesEaten) { case 0: return "$name has starved" case 1: return "$name has eaten once" default: return "$name has eaten $timesEaten times" } }}

final class Take {}final class Accepted {}final class Rejected {}final class Release {}

Page 90: concurrency gpars

…Dining Philosophers: Actors...

GPars - 90

© ASERT 2006-

2013

…class Fork extends AbstractPooledActor { String name boolean available = true

void act() { loop { react {message -> switch (message) { case Take: if (available) { available = false reply new Accepted() } else reply new Rejected() break case Release: assert !available available = true break default: throw new IllegalStateException("Cannot process the message: $message") } } } }}

def forks = (1..names.size()).collect { new Fork(name: "Fork $it") }def philosophers = (1..names.size()).collect { new Philosopher(name: names[it - 1], forks: [forks[it - 1], forks[it % names.size()]])}

Page 91: concurrency gpars

…Dining Philosophers: Actors

GPars - 91

© ASERT 2006-

2013

…import groovy.swing.*import java.awt.Fontimport static javax.swing.JFrame.*

def frame = new SwingBuilder().frame(title: 'Philosophers', defaultCloseOperation: EXIT_ON_CLOSE) { vbox { hbox { (0..<names.size()).each { i -> def widget = textField(id: names[i], text: names[i].center(14)) widget.font = new Font(widget.font.name, widget.font.style, 36) philosophers[i].propertyChange = { widget.text = philosophers[i].status.center(14) } } } }}

frame.pack()frame.visible = trueforks*.start()sleep 1000philosophers*.start()

sleep 10000

forks*.stop()forks*.join()philosophers*.stop()philosophers*.join()frame.dispose()philosophers.each { println it }

socrates has eaten 3 timesplato has eaten 3 timesaristotle has eaten 6 timesdescartes has eaten 2 timesnietzsche has eaten 5 times

Page 92: concurrency gpars

Dining Philosophers: CSP...

GPars - 92

© ASERT 2006-

2013

// inspired by similar examples at the web sites below:// http://www.cs.kent.ac.uk/projects/ofa/jcsp/// http://www.soc.napier.ac.uk/~jmk/#_Toc271192596@Grab('org.codehaus.gpars:gpars:0.10')import org.jcsp.lang.*import groovyx.gpars.csp.PARimport groovyx.gpars.csp.ALTimport static java.lang.System.currentTimeMillis

def names = ['socrates', 'plato', 'aristotle', 'descartes', 'nietzsche']enum ForkAction { Take, Release, Stop }import static ForkAction.*

class Philosopher implements CSProcess { ChannelOutput leftFork, rightFork String name def forks = [] private random = new Random() private timesEaten = 0 private start = currentTimeMillis()

void run() { while (currentTimeMillis() - start < 10000) { think() eat() } [leftFork, rightFork].each { it.write(Stop) } println toString() } …

Page 93: concurrency gpars

…Dining Philosophers: CSP...

GPars - 93

© ASERT 2006-

2013

… void think() { println "$name is thinking" sleep random.nextInt(50) }

void eat() { [leftFork, rightFork].each { it.write(Take) } println "$name is EATING" timesEaten++ sleep random.nextInt(200) [leftFork, rightFork].each { it.write(Release) } }

String toString() { switch (timesEaten) { case 0: return "$name has starved" case 1: return "$name has eaten once" default: return "$name has eaten $timesEaten times" } }}

Page 94: concurrency gpars

…Dining Philosophers: CSP...

GPars - 94

© ASERT 2006-

2013

…class Fork implements CSProcess { ChannelInput left, right private active = [0, 1] as Set

void run() { def fromPhilosopher = [left, right] def forkAlt = new ALT(fromPhilosopher) while (active) { def i = forkAlt.select() read fromPhilosopher, i, Take read fromPhilosopher, i, Release } }

void read(phil, index, expected) { if (!active.contains(index)) return def m = phil[index].read() if (m == Stop) active -= index else assert m == expected }}…

Page 95: concurrency gpars

…Dining Philosophers: CSP

GPars - 95

© ASERT 2006-

2013

…def lefts = Channel.createOne2One(names.size())def rights = Channel.createOne2One(names.size())

def philosophers = (0..<names.size()).collect { i -> return new Philosopher(leftFork: lefts[i].out(), rightFork: rights[i].out(), name: names[i])}

def forks = (0..<names.size()).collect { i -> return new Fork(left: lefts[i].in(), right: rights[(i + 1) % names.size()].in())}

def processList = philosophers + forks

new PAR(processList).run()

Page 96: concurrency gpars

Why CSP?• Amenable to proof

and analysis

GPars - 96Picture source: http://wotug.org/parallel/theory/formal/csp/Deadlock/

Page 97: concurrency gpars

Multiverse Philosophers…

GPars - 97

//@Grab('org.multiverse:multiverse-core:0.7-SNAPSHOT')//@Grab('org.multiverse:multiverse-alpha:0.7-SNAPSHOT')//@Grab('org.multiverse:multiverse-groovy:0.7-SNAPSHOT')//@GrabConfig(systemClassLoader=true, initContextClassLoader = true)// adapted multiverse Groovy example: http://git.codehaus.org/gitweb.cgi?p=multiverse.git// file: multiverse-groovy/src/test/groovy/org/multiverse/integration/org/multiverse/integration/examples/DiningPhilosphersTest.groovyimport org.multiverse.transactional.refs.BooleanRefimport org.multiverse.transactional.refs.IntRefimport static MultiverseGroovyLibrary.*

def food = new IntRef(5)def names = ['socrates', 'plato', 'aristotle', 'descartes', 'nietzsche']def forks = (1..5).collect { new Fork(id: it, free: new BooleanRef(true)) }def philosophers = (0..4).collect { new Philosopher(name: names[it], food: food, left: forks[(it + 1) % 5], right: forks[it])}def threads = philosophers.collect { new Thread(it) }threads*.start()threads*.join()philosophers.each { println it }

class Fork { int id BooleanRef free void take() { free.set(false) } void release() { free.set(true) }}

Page 98: concurrency gpars

…Multiverse Philosophers

GPars - 98

class Philosopher implements Runnable { String name Fork left, right IntRef timesEaten = new IntRef() IntRef food

void eat() { atomic(trackreads: true, explicitRetryAllowed: true) { left.free.await(true) right.free.await(true) if (food.get() > 0) { left.take(); right.take() timesEaten.inc(); sleep 10; food.dec() } } }

void think() { atomic(trackreads: true, explicitRetryAllowed: true) { left.release(); right.release() } sleep 10 }

void run() { 10.times { eat(); think() } }

String toString() { switch (timesEaten) { case 0: return "$name has starved" case 1: return "$name has eaten once" default: return "$name has eaten $timesEaten times" } }}

Page 99: concurrency gpars

Jetlang Philosophers…

GPars - 99

import org.jetlang.core.Callbackimport org.jetlang.fibers.ThreadFiberimport org.jetlang.channels.*

def names = ['socrates', 'plato', 'aristotle', 'descartes', 'nietzsche']

class Philosopher implements Callback { private random = new Random() String name int timesEaten = 0 String status def forks private channels = [new MemoryRequestChannel(), new MemoryRequestChannel()] private req = new ThreadFiber() private reply = new ThreadFiber() private responses = [] private gotFork = { it instanceof Accepted }

void start() { assert forks.size() == 2 req.start() reply.start() (0..1).each{ channels[it].subscribe(reply, forks[it]) } think() }

String toString() { switch (timesEaten) { case 0: return "$name has starved" case 1: return "$name has eaten once" default: return "$name has eaten $timesEaten times" } } …

Page 100: concurrency gpars

…Jetlang Philosophers…

GPars - 100

… void think() { println(name + ' is thinking') sleep random.nextInt(3000) (0..1).each{ AsyncRequest.withOneReply(req, channels[it], new Take(it), this); } }

void eat() { timesEaten++ println toString() sleep random.nextInt(2000) }

void onMessage(Object message) { responses << message if (responses.size() == 2) { if (responses.every(gotFork)) { eat() } responses.findAll(gotFork).each { int index = it.index channels[index].publish(req, new Release(index), forks[index]) } responses = [] think() } }}

@Immutable class Take { int index }@Immutable class Accepted { int index }@Immutable class Rejected { int index }@Immutable class Release { int index }…

Page 101: concurrency gpars

…Jetlang Philosophers

GPars - 101

…class Fork implements Callback { String name def holder = []

void onMessage(message) { def msg = message instanceof Request ? message.request : message def index = msg.index switch (msg) { case Take: if (!holder) { holder << index message.reply(new Accepted(index)) } else message.reply(new Rejected(index)) break case Release: assert holder == [index] holder = [] break default: throw new IllegalStateException("Cannot process the message: $message") } }}

def forks = (1..names.size()).collect { new Fork(name: "Fork $it") }def philosophers = (1..names.size()).collect { new Philosopher(name: names[it - 1], forks: [forks[it - 1], forks[it % names.size()]])}

philosophers*.start()sleep 10000philosophers.each { println it }

Page 102: concurrency gpars

Gruple Philosophers…

GPars - 102

import org.gruple.SpaceServiceimport org.gruple.Space

class Philosopher { private random = new Random() String name Space space private timesEaten = 0 int id, num boolean done = false

void run() { while (true) { think() if (done) return space.take(fork: id) space.take(fork: (id + 1) % num) eat() space.put(fork: id) space.put(fork: (id + 1) % num) } }

void think() { println "$name is thinking" sleep random.nextInt(500) }

void eat() { println "$name is EATING" timesEaten++ sleep random.nextInt(1000) } …

…socrates is thinkingnietzsche is thinkingdescartes is EATINGaristotle is EATINGdescartes is thinkingplato is EATINGaristotle is thinkingsocrates is EATINGplato is thinkingnietzsche is EATINGsocrates is thinkingnietzsche is thinkingdescartes is EATINGdescartes is thinking

socrates has eaten 5 timesplato has eaten 4 timesaristotle has eaten 4 timesdescartes has eaten 4 timesnietzsche has eaten 5 times

Page 103: concurrency gpars

…Gruple Philosophers

GPars - 103

… String toString() { switch (timesEaten) { case 0: return "$name has starved" case 1: return "$name has eaten once" default: return "$name has eaten $timesEaten times" } }}

def names = ['socrates', 'plato', 'aristotle', 'descartes', 'nietzsche']def diningSpace = SpaceService.getSpace('Dining')def philosophers = (0..<names.size()).collect{ new Philosopher(name: names[it], id: it, space: diningSpace, num: names.size())}(0..<names.size()).each{ diningSpace << [fork: it] }sleep 500def threads = (0..<names.size()).collect{ n -> Thread.start{ philosophers[n].run() } }sleep 10000philosophers*.done = truesleep 2000threads.join()println()philosophers.each{ println it }