GraphStream Threads and graphs : how to listen at graphs from different threads and the viewer's thread

SourceForge.net Logo

In this tutorial we will learn how to listen at graph events in a thread that does not handle the graph that produces these events We will also see how this mechanism is used to make the graph viewer work in its own thread.

How GraphStream handles threads ?

The answer is simple : most classes in GraphStream do not care at all, they are merely not synchronised. This forces the user to take all the precautions by itself. This brings the advantage of maximum speed when no threads are used, however this also bring the disadvantage to make the use of threads a difficult task. Thus, several tools are here to make the task easier.

What is the problem with threads ?

Let's see a simple example. We will use two threads, the "main" thread that is created automatically for us when the application starts, and another that we will create.

In the main thread we will create a graph. In the other thread we will create a small "stats" object that listens at the graph and updates a small data structure that store all node names and print statistics about them. This other thread will be named "listener thread", since it implements a GraphListener that is registered to the graph in the main thread.

The main thread constantly adds and removes nodes and edges in the graph. This is its only role.

The listener thread runs in a loop where it prints statistics about the nodes. For this, it constantly browses the data structure that is kept up to date by the graph listener methods.

Now imagine an event occurs on the graph, for example a node is added. Automatically, the graph listener is called. When a method is called on an object this method executes in the thread of the caller. Therefore all methods of the graph listeners are called in the thread of the graph. Here the main thread.

The listener methods modify the data structure of the "stats" object that is constantly printing the statistics in the listener thread. Therefore it becomes possible that the data structure of the "stats" object be written in the main thread and AT THE SAME TIME, be read by the "stats" object in the listener thread.

Using "proxies" to cross thread boundaries

One of the possibilities to avoid this problem is to synchronise all the methods that access the data structure that is modified by the main thread and read by the listener thread. Another way is to put a lock around the "stats" object.

This is a possible option. Another is to use the "proxy" mechanism provided by GraphStream. A proxy is a special listener that buffers the events and is able to replay them when asked to.

If the listener is in another thread than the graph, instead of registering directly the listener to the graph, you register the proxy to the graph, and then register the listener to the proxy. The proxy acts as a bridge.

Then each time something occurs in the graph, the proxy is called in the graph thread. The proxy memorise each event in a thread safe way. In the listener thread, you must call a special method regularly to get these events back. When this method is called all the events are replayed back and the listener is called for each of them.

The mechanism used to synchronise the event passing between threads use two buffers so that the synchronised part of the algorithm runs as fast as possible. This is a very simple synchronised message box.

The proxy is called org.miv.graphstream.graph.implementations.GraphListenerProxyThread.

How the graph viewer uses the proxies

The graph viewer uses this same mechanism to display the graph. It registers a proxy to the graph as listener, and listens at the proxy. This allows the viewer to run in its own thread and be reactive even if the graph thread is busy running a complex algorithm on the graph.

If the graph viewer is configured to use an automatic layout algorithm, this algorithm is automatically placed in another thread. This layout process also uses the same proxy mechanism.

Putting the layout in a thread makes sense since GraphStream is made to handle dynamic graphs. We can expect the graph to be changing continuously, and therefore the layout must adapt continuously. The layout thread is constructed so that if the layout stabilised because the graph does not change, it slows down to consume as few CPU as possible. At each change in the graph, the layout thread will return back to full speed.

The default viewer in GraphStream also tries to minimise redraw of the graph. Although the graph is "animated" when it changes, if the graph does not change, no redraw occurs, that is, the animation stops.

Using the graph viewer directly from the Swing thread and avoiding proxies

The default graph viewer uses Swing for portability reasons. It uses the Java2D features to render the graph. This choice allows to have a default graph viewer at any time without adding any third party libraries (However it is not particularly fast, and as soon as complex rendering options are used (transparencies, anti-aliasing, text, etc.) the renderer may slow down a lot). There are other viewer implementations that try to provide different features and can be a largely faster.

What is explained here only works for the default viewer.

Most of the time, when you have a program that provides a GUI, you only use the Swing thread. The main thread launches the program, then you use the event mechanism so that the program is completely event driven. If an action must occur continuously in background, you can use a Swing worker or timer.

In such a program, all work is done in the Swing thread. As the Swing viewer does not create its own thread but also uses the Swing thread it can be tempting to avoid using the proxies of GraphStream. This is easy. However you have to ensure that all the work is REALLY done in the Swing thread.

All you have to do, is to create the viewer by yourself (you cannot use the Graph.display() method). You then use a special constructor of the SwingGraphViewer that tells it to directly listen at the graph without using a proxy.

public SwingGraphViewer( Graph graph, boolean autoLayout, boolean isPanel, boolean direct ) ...

This is the last parameter that tells to use or not a proxy. If direct is true, the viewer assumes that all the code runs in the Swing thread, and that it can register as a listener directly in the graph.

When you work this way, you do not need the GraphViewerRemote. Indeed, the remote is used to command the viewer from another thread. Instead you must call the viewer methods directly. All the methods that can be found in the remote can also be found in the viewer.

This is the same for sprites. Usually you create sprites using the GraphViewerRemote. Here, you must use the GraphViewer directly. This is especially important since the real class implementation for the sprites using directly are completely different than the one used through the remote (sprites using the remote, send commands to the viewer thread through the remote, direct sprites can directly access the viewer).

Accessing the viewer directly should provide a speed increase since an intermediary proxy is skipped.

Graph layouts and threads

To do describe how to setup a layout thread that works in background and sends back coordinates in the graph using the LayoutRunner class.

css xhtml