Thanks to Mickael on twitter I ran into that article about implementing a very basic Hello World! program as a way to get into a new concurrent language or facility. The original article, titled Concurrent Hello World in Go, Erlang and C++ is all about getting to know The Go Programming Language better.
To quote the article:
The first thing I always do when playing around with a new software platform is to write a concurrent "Hello World" program. The program works as follows: One active entity (e.g. thread, Erlang process, Goroutine) has to print "Hello " and another one "World!\n" with the two active entities synchronizing with each other so that the output always is "Hello World!\n".
(defun say-hello (helloq worldq n) (dotimes (i n) (format t "Hello ") (lq:push-queue :say-world worldq) (lq:pop-queue helloq)) (lq:push-queue :quit worldq)) (defun say-world (helloq worldq) (when (eq (lq:pop-queue worldq) :say-world) (format t "World!~%") (lq:push-queue :say-hello helloq) (say-world helloq worldq))) (defun hello-world (n) (let* ((lp:*kernel* (lp:make-kernel 2)) ; a new one each time, as we end it (channel (lp:make-channel)) (helloq (lq:make-queue)) (worldq (lq:make-queue))) (lp:submit-task channel #'say-world helloq worldq) (lp:submit-task channel #'say-hello helloq worldq n) (lp:receive-result channel) (lp:receive-result channel) (lp:end-kernel)))
If you want to play locally with that code, I've been updating it to a
github project named go-hello-world, even if it's coded in CL. See the
package.lisp in there for how I did enable the local nicknames
the lparallel packages.
Beware of the REPL
In a previous version of this very article, I said that sometimes I get an
extra line feed in the output and I didn't understand why. Some great Common
Lisp folks did hint me about that: it's the REPL output that get
intermingled with the program output, and that's because the
main function was returning before the thing is over.
I've added a
receive-result call in it per worker so that it waits until the
end of the program before returning to the REPL, and that indeed fixes it. A
way to assert that is using the
time macro, which was always intermingled
with the output before. It's fixed now:
CL-USER> (time (go-hello-world:hello-world 1000)) Hello World! ... Hello World! (GO-HELLO-WORLD:HELLO-WORLD 1000) took 27,886 microseconds (0.027886 seconds) to run. 1,593 microseconds (0.001593 seconds, 5.71%) of which was spent in GC. During that period, and with 4 available CPU cores, 23,246 microseconds (0.023246 seconds) were spent in user mode 14,427 microseconds (0.014427 seconds) were spent in system mode 4,272 bytes of memory allocated. 10 minor page faults, 0 major page faults, 0 swaps. (#<PROCESS lparallel kernel shutdown manager(62) [Reset] #x30200109F65D> ...) CL-USER>
While Go language seems to bring very interesting things on the table, such as better compilation units and tools, I still think that the concurrency primitives at the core of it are easy to find in other places. Which is a good thing, as it means we know they work.
That also means that we don't need to accept Go syntax as the only way to properly solve that concurrency problem, I much prefer doing so with Common Lisp (lack of?) syntax myself.
A previous version of this article was finished and published too quickly, and the conclusion was made from a buggy version of the program. It's all fixed now. Thanks a lot to people who contributed comments so that I could fix it, and thanks again to James M. Lawrence for lparallel!