Weitere ähnliche Inhalte Ähnlich wie concurrency gpars (20) Kürzlich hochgeladen (20) concurrency gpars3. "Andy giveth and Bill taketh away"
GPars - 3
Source:HerbSutter:http://www.gotw.ca/publications/concurrency-ddj.htm
4. 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
5. Java Concurrency Best Practice?
• Java Concurrency in Practice:
–“If multiple threads access the
same mutable state variable
without 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
6. 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
©ASERT2006-2013
http://strangeloop2010.com/talk/presentation_file/14485/Johnson-DataParallelism.pdf
IncreasingAbstraction
7. 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 restrictions
e.g. Fork-join
• Hopefully deterministic semantics, not designed for I/O
– Data parallelism
• Deterministic semantics, easy, efficient, not designed for I/O
GPars - 7
©ASERT2006-2013
http://strangeloop2010.com/talk/presentation_file/14485/Johnson-DataParallelism.pdf
Each approach has some caveats
8. GPars
• http://gpars.codehaus.org/
• Library classes and DSL sugar providing
intuitive ways for Groovy developers to
handle 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
©ASERT2006-2013
9. Choosing approaches
GPars - 9
Formoredetailssee:http://gpars.codehaus.org/Concepts+compared
Parallel
Collections
Data Parallelism
Task
Parallelism
Streamed Data
Parallelism
Fork/
Join
Dataflow
operators
CSP
Actors
Dataflow tasks
Actors
Asynch fun’s
CSP
Fork/
Join
Immutable
Stm, Agents
Special collections
Synchronization
Linear Recursive
Linear
Recursive
Shared
Data
Irregular
Regular
10. Coordination approaches
GPars - 10
Source:ReGinA–GroovyinAction,2ndedition
Data Parallelism:
Fork/Join
Map/Reduce
Fixed coordination
(for collections)
Actors Explicit coordination
Safe Agents Delegated coordination
Dataflow Implicit coordination
11. Groovy concurrency value add
GPars - 11
JVM Concurrency
Threads, synchronization, locks, semaphores, Phaser,
executor, fork-join, concurrent collections, atomic objects
GPars
Map/reduce, fork/join,
asynchronous closures,
actors, CSP, agents,
dataflow concurrency
Your Application
Groovy
Thread & process
enhancements, immutability,
laziness, @WithReadLock,
@WithWriteLock, @Lazy,
@Synchronized
12. 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
13. @Immutable...
• Java Immutable Class
– As per Joshua Bloch
Effective Java
©ASERT2006-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 + ")";
}
}
14. ...@Immutable...
• Java Immutable Class
– As per Joshua Bloch
Effective Java
©ASERT2006-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
16. Approaches to managing collection storage
• Mutable • Persistent
©ASERT2006-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
17. Topics
• Intro
• Useful Groovy features for Concurrency
GPars
• Case Studies
• More Info
• Bonus Material
GPars - 17
©ASERT2006-2013
18. Choosing approaches
GPars - 18
Formoredetailssee:http://gpars.codehaus.org/Concepts+compared
Parallel
Collections
Data Parallelism
Task
Parallelism
Streamed Data
Parallelism
Fork/
Join
Dataflow
operators
CSP
Actors
Dataflow tasks
Actors
Asynch fun’s
CSP
Fork/
Join
Immutable
Stm, Agents
Special collections
Synchronization
Linear Recursive
Linear
Recursive
Shared
Data
Irregular
Regular
19. Groovy Sequential Collection
GPars - 19
©ASERT2006-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
20. GPars Parallel Collections…
GPars - 20
©ASERT2006-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
}
21. …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
©ASERT2006-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
}
22. Parallel Collection Variations
• Apply some Groovy metaprogramming
GPars - 22
©ASERT2006-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..5
ParallelEnhancer.enhanceInstance(nums)
assert [1, 4, 9, 16, 25] == nums.collectParallel{ it * it }
23. GPars parallel methods for collections
Transparent 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
FormoredetailsseeReGinAortheGParsdocumentation
24. GPars: Map-Reduce
GPars - 24
©ASERT2006-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
}
25. GPars parallel array methods
Method Return Type
combine(initValue) { ... } Map
filter { ... } Parallel array
collection Collection
groupBy { ... } Map
map { ... } Parallel array
max() T
max { ... } T
min() T
min { ... } T
reduce { ... } T
reduce(seed) { ... } T
size() int
sort { ... } Parallel array
sum() T
parallel // on a Collection Parallel array
GPars - 25
FormoredetailsseeReGinAortheGParsdocumentation
26. Parallel Collections vs Map-Reduce
GPars - 26
Fork Fork
JoinJoin
Map
Map
Reduce
Map
Map
Reduce
Reduce
Map
Filter
FilterMap
27. Concurrency challenge…
• Suppose we have the following
calculation involving several functions:
• And we want to use our available cores …
GPars - 27
©ASERT2006-2013
// example adapted from Parallel Programming with .Net
def (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
28. …Concurrency challenge…
• We can analyse the example’s task graph:
GPars - 28
©ASERT2006-2013
// example adapted from Parallel Programming with .Net
def (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
29. …Concurrency challenge…
• Manually using asynchronous functions:
GPars - 29
©ASERT2006-2013
// example adapted from Parallel Programming with .Net
def (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
30. …Concurrency challenge
• And with GPars Dataflows:
GPars - 30
©ASERT2006-2013
def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 +
[{ x, y -> x + y }]
import groovyx.gpars.dataflow.Dataflows
import 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
31. …Concurrency challenge
• And with GPars Dataflows:
GPars - 31
©ASERT2006-2013
def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 +
[{ x, y -> x + y }]
import groovyx.gpars.dataflow.Dataflows
import 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
32. GPars: Dataflows...
GPars - 32
©ASERT2006-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
*
33. ...GPars: Dataflows...
• Evaluating:
GPars - 33
©ASERT2006-2013
import groovyx.gpars.dataflow.DataFlows
import 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?
34. ...GPars: Dataflows...
• Naive attempt for loops
GPars - 34
©ASERT2006-2013
import groovyx.gpars.dataflow.Dataflows
import 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
35. ...GPars: Dataflows...
GPars - 35
©ASERT2006-2013
import groovyx.gpars.dataflow.DataflowStream
import 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
10
10
20
20
4
5
4
5
a
+-
*
84
75
384
375
36. ...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
©ASERT2006-2013
import groovyx.gpars.dataflow.Dataflows
import static groovyx.gpars.dataflow.Dataflow.task
final flow = new Dataflows()
task { flow.x = flow.y }
task { flow.y = flow.x }
37. GPars: Dataflow Sieve
GPars - 37
©ASERT2006-2013
final int requestedPrimeNumberCount = 1000
final 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 = initialChannel
requestedPrimeNumberCount.times {
int prime = currentOutput.val
println "Found: $prime"
currentOutput = filter(currentOutput, prime)
}
Source: http://groovyconsole.appspot.com/script/235002
38. 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
©ASERT2006-2013
39. …GPars: Actors...
• Class with the following lifecycle &
methods
– But also DSL sugar & enhancements
GPars - 39
©ASERT2006-2013
start()
stop()
act()
send(msg)
sendAndWait(msg)
loop { }
react { msg -> }
msg.reply(replyMsg)
receive()
join()
40. …GPars: Actors...
GPars - 40
©ASERT2006-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 Groovy
Sorry, please try again
You voted for Groovy
You voted for Go
Sorry, please try again
41. …GPars: Actors...
GPars - 41
©ASERT2006-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 Groovy
You voted for JRuby
Sorry, please try again
You voted for Groovy
Sorry, please try again
Sorry, please try again
42. …GPars: Actors
GPars - 42
©ASERT2006-2013
import groovyx.gpars.activeobject.*
@ActiveObject
class 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 again
You voted for Groovy
You voted for Pascal
Sorry, please try again
You voted for Groovy
43. 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
©ASERT2006-2013
44. …Agents…
GPars - 44
©ASERT2006-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 result
println result.size()
Unsafe!
45. …Agents…
GPars - 45
©ASERT2006-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 join
String result = agent.val
println result
println result.size()
46. …Agents
GPars - 46
©ASERT2006-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.val
println result
println result.size()
47. Software Transactional Memory…
GPars - 47
©ASERT2006-2013
@Grab('org.multiverse:multiverse-beta:0.7-RC-1')
import org.multiverse.api.references.LongRef
import static groovyx.gpars.stm.GParsStm.atomic
import 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()
}
}
// ...
48. …Software Transactional Memory
GPars - 48
©ASERT2006-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 150
try {
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()
49. Topics
• Intro
• Useful Groovy features for Concurrency
• GPars
Case Studies
Web Testing
Word Split
• More Info
• Bonus Material
GPars - 49
©ASERT2006-2013
50. GPars for testing
GPars - 50
©ASERT2006-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) {
...
51. Topics
• Intro
• Useful Groovy features for Concurrency
• GPars
Case Studies
Web Testing
Word Split
• More Info
• Bonus Material
GPars - 51
©ASERT2006-2013
52. Word Split with Fortress
GPars - 52
©ASERT2006-2013
Guy Steele’s StrangeLoop keynote (from slide 52 onwards for several slides):
http://strangeloop2010.com/talk/presentation_file/14299/GuySteele-parallel.pdf
53. Word Split…
GPars - 53
©ASERT2006-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']
54. Word Split…
GPars - 54
©ASERT2006-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
}
55. Word Split…
GPars - 55
©ASERT2006-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
}
58. Segment(left1, m1, right1) Segment(left2, m2, right2)
Segment(left1, m1 + [ ? ] + m2, right2)
…Word Split…
GPars - 58
©ASERT2006-2013
59. …Word Split…
GPars - 59
©ASERT2006-2013
class 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) }
}
60. …Word Split…
GPars - 60
©ASERT2006-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']
63. …Word Split…
GPars - 63
©ASERT2006-2013
THREADS = 4
def 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()
}
64. …Word Split…
GPars - 64
©ASERT2006-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()
}
}
65. …Guy Steele example in Groovy…
GPars - 65
©ASERT2006-2013
def 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
66. …Guy Steele example in Groovy…
GPars - 66
©ASERT2006-2013
GRANULARITY_THRESHHOLD = 10
THREADS = 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
67. …Guy Steele example in Groovy
GPars - 67
©ASERT2006-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
68. Topics
• Intro
• Useful Groovy features for Concurrency
• Gpars
• Case Studies
More Info
• Bonus Material
GPars - 68
©ASERT2006-2013
69. 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
70. 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
– user@groovy.codehaus.org
• 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
73. Bonus Material
• Other concurrency options
– Jetlang
– JPPF
– Multiverse
– Gruple
– Cascading
– GridGain
– ConTest
GPars - 73
©ASERT2006-2013
74. Lightweight threads: Jetlang
• Jetlang
– A high performance threading library
– http://code.google.com/p/jetlang/
GPars - 74
import org.jetlang.fibers.ThreadFiber
import org.jetlang.channels.MemoryRequestChannel
import org.jetlang.channels.AsyncRequest
def req = new ThreadFiber() // or pool
def 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 1000
req.dispose()
reply.dispose()
12
75. 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 task
def results = client.submit(job)
for (t in results) {
if (t.exception) throw t.exception
println "Result: " + t.result
}
76. 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
77. …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]
78. 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().name
while(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 jo
space.put(result)
}
...
80. 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)
81. 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)
82. …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())
83. 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
84. ...Testing multi-threaded applications: ConTest
GPars - 84
NUM = 5
count = 0
def incThread = { n -> Thread.start{
sleep n*10
//synchronized(ParalInc) {
count++
//}
} }
def threads = (1..NUM).collect(incThread)
threads.each{ it.join() }
assert count == NUM
targetClasses = ParalInc
timeoutTampering = true
noiseFrequency = 500
strength = 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
85. Bonus Material
• Dining Philosopher’s Case Study
– GPars Actors
– GPars CSP
– Multiverse
– Jetlang
– Gruple
GPars - 85
©ASERT2006-2013
86. Dining Philosophers…
GPars - 86
Philosopher
Thinking | Eating
Philosopher
Thinking | Eating
Philosopher
Thinking | Eating
Philosopher
Thinking | Eating
Philosopher
Thinking | Eating
Fork
Available | InUse
Fork
Available | InUse
Fork
Available | InUse
Fork
Available | InUse
Fork
Available | InUse
87. …Dining Philosophers
GPars - 87
Philosopher
Thinking | Eating
Philosopher
Thinking | Eating
Philosopher
Thinking | Eating
Philosopher
Thinking | Eating
Philosopher
Thinking | Eating
Fork
Available | InUse
Fork
Available | InUse
Fork
Available | InUse
Fork
Available | InUse
Fork
Available | InUse
Accepted | Rejected
Take | Release Take | Release
88. Dining Philosophers: Actors...
GPars - 88
©ASERT2006-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()
}
}
}
}
}
89. …Dining Philosophers: Actors...
GPars - 89
©ASERT2006-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 {}
90. …Dining Philosophers: Actors...
GPars - 90
©ASERT2006-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()]])
}
91. …Dining Philosophers: Actors
GPars - 91
©ASERT2006-2013
…
import groovy.swing.*
import java.awt.Font
import 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 = true
forks*.start()
sleep 1000
philosophers*.start()
sleep 10000
forks*.stop()
forks*.join()
philosophers*.stop()
philosophers*.join()
frame.dispose()
philosophers.each { println it }
socrates has eaten 3 times
plato has eaten 3 times
aristotle has eaten 6 times
descartes has eaten 2 times
nietzsche has eaten 5 times
92. Dining Philosophers: CSP...
GPars - 92
©ASERT2006-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.PAR
import groovyx.gpars.csp.ALT
import 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()
}
…
93. …Dining Philosophers: CSP...
GPars - 93
©ASERT2006-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"
}
}
}
94. …Dining Philosophers: CSP...
GPars - 94
©ASERT2006-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
}
}
…
95. …Dining Philosophers: CSP
GPars - 95
©ASERT2006-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()
96. Why CSP?
• Amenable to proof
and analysis
GPars - 96
Picture source: http://wotug.org/parallel/theory/formal/csp/Deadlock/
97. 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.groovy
import org.multiverse.transactional.refs.BooleanRef
import org.multiverse.transactional.refs.IntRef
import 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) }
}
98. …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"
}
}
}
99. Jetlang Philosophers…
GPars - 99
import org.jetlang.core.Callback
import org.jetlang.fibers.ThreadFiber
import 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"
}
}
100. …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 }
…
101. …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 10000
philosophers.each { println it }
102. Gruple Philosophers…
GPars - 102
import org.gruple.SpaceService
import 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 thinking
nietzsche is thinking
descartes is EATING
aristotle is EATING
descartes is thinking
plato is EATING
aristotle is thinking
socrates is EATING
plato is thinking
nietzsche is EATING
socrates is thinking
nietzsche is thinking
descartes is EATING
descartes is thinking
socrates has eaten 5 times
plato has eaten 4 times
aristotle has eaten 4 times
descartes has eaten 4 times
nietzsche has eaten 5 times
103. …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 500
def threads = (0..<names.size()).collect{ n -> Thread.start{ philosophers[n].run() } }
sleep 10000
philosophers*.done = true
sleep 2000
threads.join()
println()
philosophers.each{ println it }