Givvy Says: Concurrent Programming is Not That Hard
Givvy Says: Concurrent Programming is Not That Hard
Join the DZone community and get the full member experience.Join For Free
Get the Edge with a Professional Java IDE. 30-day free trial.
Perhaps it's somewhat unusual to present a client-side programming language by showing an example of Concurrent Programming. Especially if you already saw a bunch of GVision GUI applications in action in an introductory screencast. Actually, we are so accustomed to consider a Web application as the ultimate form of distributed application, and accordingly to tie the client always and only with the presentation layer, that we get confused or are skeptical thinking of the client as a simple processing node.
But I ask you... Today we have multi-core architectures even on the cheapest laptops. Why only server side languages should be able to exploit their computing power ? Why should not they also be used at the client side for running concurrent applications, or perhaps for doing some generic background processing ? In truth, there is no valid argument against this.
However, I opted for this Givvy snippet also for more down-to-earth reasons. First, it is fairly common and simple, but not useless. Many programming languages use it as an example, so you can easily compare the different implementations. Furthermore, it is also effective in demonstrating my earlier claim where I was saying that the implementation of the Actor Model provided by Givvy simplifies the approach to Concurrent Programming even by the OO developer (but also programming as a whole. An example on the fly... any mutable compound data structure can be safely changed while being iterated. No need to use ad-hoc iterator objects).
Of course, if you want to try yourself the program, you will need both the runtime (GVNode) and the control node (cNode). I remind you in fact that GVision is a distributed architecture, therefore you necessarily have to install both the client and the server side. The concept is quite similar to playing with a Web application. There, you need the Web/Application server for delivering the application, as well as the Web browser to see how it behaves. What is different in GVision is that the processing engine is shifted from the server to the client side (I do assume that you have by now realized that we are not talking about a monolithic RIA model, given the dynamic infrastructure of GVision).
If you follow the instructions on this page, you will enjoy the simplicity of this infrastructure, and within minutes you will be able to experiment, in a distributed multi-connection graphical REPL, all the examples provided in the GVision support distribution (yet not so many, really, but each one is noteworthy), along with all those which will be published in future, of course. You will also learn as to create and launch a distributed Givvy program is easier and faster than creating and displaying a HTML page (ok, this is pretty opinionated, but I hope you grant me a little room to manoeuvre). Givvy, indeed, is an expression-oriented programming language –– i.e. any statement is actually an expression that returns a value –– so for instance, using the REPL, you can start with a basic GUI program, and gradually change it iteratively, until you get, at client side, the result you want.
Clearly, Givvy applications are consistent across all Operating Systems supported by the JVM, so, for example, you can have a Windows or a MacOSX GVNode talking with a Linux cNode, as well as the opposite, a Linux GVNode and a Windows cNode. Any combination is allowed.
Givvy and Table Tennis
The "Ping-Pong" is the classic Erlang example. It's a simple dialog between two actors, so I think that the snippet should be easily –– more or less :-) –– understood even by an Object-Oriented developer.
So, let's start to explore Givvy by analyzing the most prominent construct of the language, the Task.
At first sight, if you are an OO developer, you could suppose a Task is quite similar to a Class, with methods and some sort of mutable identifier, so
- :(task pong, int count) seems a kind of declaration
- the :@ operator could be a method call to an other Task
- the this.finalize statement destroys the Task
- the & operator creates a new Task instance
But... as Givvy is not an OO programming language, so a Task may not be a Class.
It's still a container for data and operations, but it's not inheritable, you can't derivate sub-classes. Actually, a Task is primarily a "lightweight process",
–– Of course, the number of Tasks that can be created mostly depends on the amount of memory that the JVM can adddress. ––
whose distinguishing feature is to allow to alter its state, or to run any computation it provides, only through a queue (known as mailbox) from which all method invocations (in reality messages) pushed into from other Tasks are pulled out and executed one by one. This way, there is never concurrent access to the Task's state, unlike what could happen in most OO languages, so there is no need for guard objects (locks) to protect it.
This is what you'd generally expect from a traditional implementation of the Actor Model. In Givvy, however, it was designed under a quite different philosophy compared to that adopted by Erlang or Scala, for example. I will mention here only some of the concepts that characterize the Givvy's implementation. The remaining, concerning Schedulers, Message Routing and Lazy Futures, will be discussed in a later article.
- The languages above mentioned generally lean on a selection data structure and pattern matching for filtering messages to process, whereas, in Givvy, each message causes the execution of a function. Although functional, the former approach implies that the programmer will not be able to apply the same logic writing concurrent and non-concurrent programs (this is mostly true for Scala), and, in addition, it might be also perceived, in my opinion, as less "natural" by a OO programmer looking at the Actor Model, when compared to the Givvy approach.
- Givvy, besides, add to the Model a new data structure known as Core, which represents the execution domain of a Task group. Any Task, indeed, is always bound to a specific Core, which is also the container and the real supervisor of the mailbox that receives the messages addressed to the Task group.
What basically happens within this Model, then, is that
- a message –– in the form of a function call –– is sent by a sender Task, say in the A domain, to the mailbox of the Core, say it the B domain, where the receiver Task is located
- the Core B wakes up the receiver Task when it realizes that there is a new message in its mailbox
- the receiver Task processes the requested job and returns to the dormant state again
In such a situation the receiver Task, while processing the job, is also able to safely communicate with any other Task in the A domain through synchronous messages, denoted by the :: operator, thanks to the fact that deadlocks due to circular invocations and concurrent accesses to the Tasks' state simply can't happen. This provides a big boost to the domain's overall performance.
In the "Ping-Pong" example, on the opposite, the message dispatching is always asynchronous, as indicated by the :@ operator. That is, ping does not interrupt its flow of execution waiting for a response from pong.
Even in this case, however, performance are not so negatively affected, since, also here, at spawning –– denoted by the & operator ––
both Tasks are bounded to a common domain, the SysCore.
–– The SysCore can generally be omitted within the  syntax, since it's already created for us by the system. Otherwise you might as well write
[ "SysCore" ]
Of course, Givvy also allows synchronous message-passing between Tasks placed in different domains, but since the topic is thorny and challenging, it will be then discussed in another post.
Quickly on the remaining points :
- Only Task's functions declared as public have global visibility and are therefore callable from other Tasks. Any non-public function –– i.e. without the public modifier –– represents a private function of the Task; it can only be called inside its scope.
Givvy, thanks to the JVM, is Garbage-Collected, so it's not mandatory to manually destroy every Task, but the finalize statement might be necessary for those Tasks containing public identifiers (for example... Tasks returning lazy futures are always automatically GCed, as they do not have any kind of public identifier).
The syntax :() represents an injection (Givvy doesnt'have constructors).
At Task spawning, the injections allow to initialize one or more public and/or private Task's identifier, and to optionally assign them a default value. A Task can have zero or more injections, but each one must differ from the others in the number of identifiers. On the other hand, more injections can have the same identifier.
The injections accordingly do not have a body. They do not require code since their job is simply to initialize one or more Task's identifiers. Nevertheless, every section of code outside any of the Task's functions, i.e. at Task scope level, is executed at spawning time and then never again. This particular code is known as spawn code.
We still miss several features of the  operator,
but I'd say it's so prominent in Givvy to deserve an ad-hoc post. For the moment it's enough to say that any Task is always scheduled by the programmer. When we talk about Schedulers, I'll show you the advantages of this architectural decision. However, those eager can still have a look at this short presentation the summarizes all the features provided by the Givvy's concurrency model.
Opinions expressed by DZone contributors are their own.