In this guide we want to show how it is possible to execute easily multiple threads in the runtime library. The end-user can open multiple console tabs. Each console tab has a Java thread associated that executes a Jekejeke Prolog interpreter which in turn executes a Prolog query answer loop. Each Jekejeke Prolog interpreter has a standard input and standard output stream which are associated with the console. These streams are thread safe and can thus be accessed by multiple threads. We will use one standard output stream as a logging device for our multiple threads demonstration.
The logging device can be initialized by the following init
init :- current_output(X), assertz(console(X)).
The init predicate will first access the standard output of the Jekejeke Prolog interpreter that executes the predicate. And then asserts the corresponding stream as a console fact. Access-ing facts and rules is also thread safe so that the console fact can be later accessed by multi-ple threads. To init the logging device we use the main tab that comes up when starting up the interpreter. The consulting of the Prolog text is not shown:
A new console tab and thus thread can be created by the menu item Window | New Tab An additional console tab can be recognized by the name “Thread #<Number>” in the window title. The “<Number>” is the running number of the additional console tabs. To test whether we can write into the main tab, we use the following additional log predicate:
console_log(X,Y) :- atom_split(T, '', ['Process ',X,': ',Y,'\n']), console(Z), write(Z, T), flush_output(Z).
This predicate will first prepare an atom with the desired text and then write it in one go to the logging device. Since the logging device writes to the main tab we will not see it in the addi-tional console tab. All that can be seen in the additional console tab is that the log predicate succeeds:
But when we turn to the main tab, we see the text that has been logged to the logging device which is the standard output of the main tab. That we are in the main tab is indicated by the name “Init” in the tab title. We see the text as follows:
We will now go on and define a perpetual process. There are different approaches to realize perpetual process in Prolog. One approach consists of using argument variables to carry around a state. The programming pattern looks as follows:
process(S1) :- transition(S1,S2), process(S2).
This approach does not yet fully work in Jekejeke Prolog. The stack frame elimination will keep the number of choice points constant when the predicate transition/1 is deterministic. But the variable counter will currently increment indefinitely, since we do not yet have imple-mented a variable counter compression method. This leads to inconsistencies in the lexical comparison of variables and in the output of variables. Therefore we propose a method that uses the database for the state. The programming pattern looks as follows:
process(ID) :- repeat, retract(state(ID,S1)), transition(S1,S2), assertz(state(ID,S2)), fail.
The second programming pattern works since the predicate repeat/0 does not increment the variable counter. The example we are using here is even simpler. We don’t have a state at all but use still the repeat programming pattern to avoid the variable counter problem. Our example perpetual process will indefinitely write “Ha” and “Tschi” on the logging device. It is defined as follows:
process(X) :- repeat, console_log(X,'Ha'), console_log(X,'Tschi') fail.
We can start the example perpetual process via process(‘A’) on the main tab. We will see that the main console is indefinitely filled with the logging of “Ha” and “Tschi”. The console content will not grow indefinitely since it is itself a first-in-last-out buffer. Also the console will not fill very quickly since the output is throttled. Both parameters, the buffer length and the scroll delay, can be set in the terminal settings.
We can now start an additional example perpetual process via process(‘B’) in the other tab. This process will also fill the main tab since the main tab is our logging device. There is no fixed order when the two processes will write in the logging device. They are true Java threads and we will observe true interleaving. For example in the below screenshot we see that between the ‘Ha’ of process B and the ‘Tschi’ of process B, the process A is able to issue 3 write instructions.
The standard output can be put on hold anytime by pressing Ctrl-Y and resumed by pressing Ctrl-Y again. When the standard output is put on hold, the current write instruction on this stream will be blocked and subsequently all other threads also trying to execute a write instruc-tion on this stream will be also blocked. Thread execution can be stopped by invoking the menu item Run | Abort or pressing Ctrl-Period. When we do this on the main tab, the process(‘A’) will be aborted, but the process(‘B’) will still continue:
To stop the thread of the process(‘B’) we have to issue the menu item Run | Abort or press Ctrl-Period from the other tab. In our example when we do this, we will not have any thread anymore writing to the logging device and thus the main tab will turn silent. The menu item Run | Abort does nothing else than inject an exception to the running process. The particular exception is handled by the query answer loop. Therefore we will not see the exception message or its context:
Aborting a thread is irreversible. Alternatively in the development environment the menu item Debug | Pause can be used respectively Ctrl-Comma can be pressed to put the thread into trace mode. This will allow to inspect the currently executing instructions in more detail and to decide whether an abort is really necessary.