GPars is an open source project to bring many concurrency abstractions to Java and/or Groovy. It was bundled in Groovy for a short time and then later pulled out.
It includes parallel map/reduce, Actors, Dataflow, and many other concurrency models. Although any concurrency library made for Java, such as RxJava, Project Reactor, or Akka, can be used with Groovy, GPars was made with Groovy specifically in mind.
Getting Started
To get started you first need to include GPars in your project. For example, in a Gradle build file, add the following to your
dependencies block
:
compile "org.codehaus.gpars:gpars:1.2.1"
For a maven build, add the following to your dependencies:
<dependency>
<groupId>org.codehaus.gpars</groupId>
<artifactId>gpars</artifactId>
<version>1.2.1</version>
</dependency>
Parallel Map Reduce
In this section we will use a list of students with graduation years and GPAs.
class Student { int graduationYear; double gpa; }
// create a list of students
Collection<Student> students = new ArrayList<>()
You perform
parallel map/reduce with the GPars library in the following way:
1 GParsPool.withPool {
2 // a map-reduce functional style
3 def bestGpa = students.parallel
4 .filter{ s -> s.graduationYear==Student.THIS_YEAR }
5 .map{ s -> s.gpa }
6 .max()
7 }
The static method GParsPool.withPool takes in a closure and augments any Collection with several methods (using Groovy’s Category mechanism). The parallel method actually creates a ParallelArray (JSR-166) from the given Collection and uses it with a thin wrapper around it.
Actors
The Actor design pattern
is a useful pattern for developing concurrent software. In this pattern, each Actor executes in its own thread and manipulates its own data. The data cannot be manipulated by any other thread. Messages are passed (internally by GPars) between the Actors to cause them to change the data. You can also make stateless Actors.
When data can be changed by only one thread at a time, it’s called thread-safe.
0 @Grab("org.codehaus.gpars:gpars:1.2.1")
1 import groovyx.gpars.actor.Actor
2 import groovyx.gpars.actor.DefaultActor
3
4 class Dragon extends DefaultActor {
5 int age
6
7 void afterStart() {
8 age = new Random().nextInt(1000) + 1
9 }
11 void act() {
12 loop {
13 react { int num ->
14 if (num > age)
15 reply 'too old'
16 else if (num < age)
17 reply 'too young'
18 else {
19 reply 'you guessed right!'
20 terminate()
21 }
22 }
23 }
24 }
25 }
26 // Guesses the age of the Dragon
27 class Guesser extends DefaultActor {
28 String name
29 Actor server
30 int myNum
31
32 void act() {
33 loop {
34 myNum = new Random().nextInt(1000) + 1
35 server.send myNum
36 react {
37 switch (it) {
38 case 'too old': println "$name: $myNum was too old"; break
39 case 'too young': println "$name: $myNum was too young"; break
40 default: println "$name: I won $myNum"; terminate(); break
41 }
42 }
43 }
44 }
45 }
46
47 def master = new Dragon().start()
48 def player = new Guesser(name: 'Guesser', server: master).start()
49
50 //this forces main thread to live until both actors stop
51 [master, player]*.join()
Here the Dragon class
starts with some random age between 1 and 1000. It then reacts to a given number, replying if the number is too big, too small, or the same as its age. The Guesser class
loops, generating a random guess each time through the loop and sending it to the Dragon (referred to as server). The Guesser then reacts to the message from the Dragon and terminates when the correct age was guessed.
The output will be something like the following:
Guesser: 236 was too young
... many other guesses
Guesser: 819 was too old
Guesser: I won 527
More GPars
For more information on other parts of GPars, such as Agents (which are much like Actors with functions as messages), Dataflow (an alternative concurrency model with deterministic behavior), and STM (Software Transactional Memory, which gives developers transactional syntax for dealing with in-memory state), please see GPars’ excellent guide online.