
Concurrency I can finally understand and write correctly - jordigh
http://jordi.inversethought.com/blog/advent-of-d/#day18
======
moomin
People always used to ask me for advice on multithreading, because I had a
reputation for multithreaded code that worked. I recommended they use Retlang
(there's a Java version called Jetlang), which works on a shared-nothing
message passing basis (and is blindingly fast). They tended to say they didn't
want a library and just wanted to use locks and threads. I said that not doing
that was how come I had a reputation for correctness.

~~~
bsder
They're in Java and writing locks and threads. Sigh.

java.util.concurrent is probably the single best concurrency library I have
ever used. Bar none.

When I'm writing in Java, and I find myself writing a lock, my first reaction
now is: "This is okay for exploration, but which data structure from
java.util.concurrent am I going to have swap in here when it's all done. And
can I do it now and save myself a lot of grief."

It only took me 7+ years to come to this realization. Apparently I'm a slow
learner. :(

~~~
cup-of-tea
They're using Java instead of Clojure. Sigh.

~~~
joncampbelldev
I'm saying this as a massive clojure fanboy: java.util.concurrent is an
amazing set of concurrency utilities and is the building block for many of
clojure's concurrency features. It's definitely worth knowing when writing
performant java AND clojure (you can wrap the little bit you need so you don't
have to scatter java interop all over your codebase). Sometimes a (swap! atom
fn) doesn't cut it.

------
herodotus
When Steve Jobs was away due to his illness, Bertrand Serlet presented Mac OS
10.6 (Snow Leopard) at the WWDC June 2009 with the startling slide that said
"0 new features". There was sustained applause from the crowd. But in fact
there was a very significant under-the-hood new feature called "Grand Central
Dispatch". It is a low-level API that removes a lot of the pain of
multithreading. Essential for OS X and iOS developers, as it turns out.

~~~
FPGAhacker
Dear God I wish they would do another 0 new features release.

It used to be that the excuse was bug fixes don’t sell. Maybe not, but buggy
software does lose customers. They may bring in the newbies with features, but
you retain with bug fixes.

~~~
overcast
I really don't think the general population abandons entire operating system
ecosystems, because of buggy software.

~~~
pythonaut_16
I think in part they do, otherwise OS X wouldn't have gained as nearly as much
marketshare on the promise that "It just works"

~~~
overcast
OS X gained market share because of the insane popularity of the iPod, and
subsequently the iPhone. Gained nearly 4% post iPhone launch.

------
alfanerd
In my 30+ years experience of concurrency, the shared-nothing message model
makes it relative easy to reason about concurrency (since every actor is
single threaded).

I have worked with Java, C# and Python, and for each I have developed a
concurrent object framework, making it quite easy to work with multible
channels of incoming events, for example for process control systems with
barcode-scanners, scales, I/O-busses etc. connected to the system

I think ponylang does a pretty good implementation of the Actor model.

Feel free to have a look at the Python impl. of the framework
(www.github.com/pylots/pyworks)

~~~
pjmlp
In a way it is ironic that we went full speed into threads, shared memory and
plugins, only to go back into processes, messages and sandboxing as a means to
tame multi-core programming and safety.

~~~
platz
Concurrency abstractions are still built on primitives that involve locks and
memory barriers.

~~~
pjmlp
Just like the old UNIX process IPC or micro-kernels communication is.

------
KingMob
I'm surprised nobody's mentioned Clojure in the comments. Its concurrency
abilities are top-notch, straightforward to use, and immutable data makes it
dead-simple to avoid issues.

~~~
kqr
It also occupies a different subspace. D is still relatively close to the
metal.

~~~
KingMob
That's fair. Though the JVM is more performant than most people think.

------
psyc
In the distant past, I did low-level multithreaded programming in the
patchwork style of synchronizing access to various data. Partly because the
codebase already did it that way and partly because i didn’t know better.
These days I would never torture myself with such a thing. I still do
concurrency sans framework, but like in the article - lock free with no
sharing. Copying a block of data when it’s ready is cheap compared to
computing it.

~~~
beached_whale
In addition, you can break your program down into flows of tasks up front.
Thinking about the synchronization explicitly helps too, often there is too
much unnecessary communication.

I think that the data structures used by D for message passing are the real
interesting part here. Akin to not using raw pointers, don't use raw data
structures to convey state updates and let the owner of that state do it
themselves with a queue or some abstraction for messages.

------
jordigh
Hey, if you're curious about anything else I wrote in that blog post, just ask
me. I thought the concurrency was a big highlight, but there's lots of other
parts of D that I would love to talk about.

As I said, the whole thing was very enjoyable. I had lots of fun using D.

:-)

~~~
agumonkey
yeah but what about recursive named emacs kmacros ?! jk

nice post

------
zoffix222
I like concurrency in Rakudo Perl 6.
([https://docs.perl6.org/language/concurrency](https://docs.perl6.org/language/concurrency))

Especially the .hyper method that turns a regular sequence into hyper sequence
that gets processed in parallel. First 5000 prime numbers:

    
    
        # single-thread, takes 14.893s
        put ^∞ .grep(*.is-prime).head: 5000
    
        # multi-threaded, takes 8.853s on my 2-core box
        put ^∞ .hyper.grep(*.is-prime).head: 5000

~~~
zzzcpan
Concurrency is not a first class citizen in Perl 6 though. As with Perl 5 the
best you can hope for is actor model implemented on top of promises/futures.

~~~
zoffix222
What would make it a first-class citizen?

Even an empty Rakudo Perl 6 program is a multi-threaded program, as the
dynamic optimizer runs on a separate thread. I'd figure if that's possible,
concurrency/parallelism would be quite a first-class citizen, not a best hope
on top of something.

~~~
zzzcpan
Language support for actor model as opposed to shared memory multithreading
would make concurrency a first class citizen.

~~~
zoffix222
Thanks.

------
jscipione
BeOS had an threading implementation like this in C++ in 1999:
[https://www.haiku-os.org/legacy-
docs/bebook/TheKernelKit_Thr...](https://www.haiku-os.org/legacy-
docs/bebook/TheKernelKit_ThreadsAndTeams_Overview.html)

~~~
dnautics
I used to program in this, and it was clearly inspired by the actor model (in
retrospect, now that I have been programming in elixir for a while). Lots of
void* passing, though.

Initially the actor model was designed in the 70s.

------
gbrown
For a C++ implementation of actor patterns, see: [https://www.actor-
framework.org/](https://www.actor-framework.org/)

------
zmmmmm
A shout out to the GPars framework [1] which makes actor based coding
stunningly easy in Groovy / Java. I'm kind of shocked by the performance I can
get out of it, to be honest. I once assumed that for trivial tasks (reading
lines from a file, transforming them, writing out result etc), a parallel
library would lose a lot of overhead in thread synchronisation. However I find
that I can trivially construct data flows that massively outperform single
threaded C code in Groovy. Which is to say, whatever overhead there is from
thread synchronisation is dwarfed by the benefit of parallelising reading,
computation and writing. If I had to think hard about concurrency to do it (as
I would in C) I would probably never attempt it for simple programs, but GPars
makes it so easy, safe and completely portable that there's really no reason
not to.

[1] [http://www.gpars.org/](http://www.gpars.org/)

------
eecc
On the other hand an Actor modelled implementation can - will - quickly get
out of hand if you start using it to directly represent a business domain.
Actor signature is Any -> Unit which essentially means “computes anything”
plus there’s internal state (become), message queue size, problems with
deterministic testability and so on.. I think Actors as an alternative to
Threads, particularly valuable for its distributed supervisor trees, and used
to build more abstract consumers for a strongly typed model of business
representations.

------
bjoli
It amazes me to no end that CML-style concurrency is not used more often.
(Even though Go is sort of almost there)

I have been using guile fibers for some time, and it is a marvel! Not only can
I write most code as if it was single threaded, but I can mix fibers code with
pthreads and use fibers message passing when I need it.

------
jonalmeida
If anyone is looking for another language that also uses the actor model for
concurrency and has very similar ways of handling it like D, then checkout
Erlang. It's language lessons are clearly still relevant today. :)

~~~
syeaj
Would any sort of actor model language have the raw throughput for a AAA game?
I get how this model is nice for the web, for example, but I'm wondering why
it doesn't get used for high performance computing (that I know of).

~~~
macintux
The Erlang VM (BEAM) is unusual in that it, like the languages it hosts, is
very opinionated.

It is designed for robustness, scalability, concurrency, distributed
environments. And immutable data. So far as I know, you literally cannot
implement a language with mutability on the VM.

So, raw performance will never be its thing.

In general, I think the actor model could achieve high performance, but
perhaps only if messaging is syntactic instead of truly distributed with
mailboxes, network transparency, etc.

~~~
mercer
I'm a beginner, so by all means correct me if I'm wrong, but as I understand
BEAM is relatively good when it comes to 'soft realtime', and especially when
'latency' is a concern (in part due to per-process GC?).

Am I correct in thinking that this would be pretty okay for most games, AAA or
not? Would an FPS be possible (quick updates, small messages)? Or a World Of
Warcraft or Sea of Thieves style game with many actors that need sort-of-
realtime performance but don't rely on it entirely?

I've been looking into creating a game, and I'm also learning Elixir, so I'm
curious what would be realistic when combining both.

~~~
jerf
In performance terms, Erlang is broadly speaking a scripting language, in the
10-20x slower than C range. It would be unusable for a AAA game because even
using Erlang without any concurrency, it's too slow. It will get even slower
if you do what you might be inclined to do for a game and make a separate
process for every entity in the game and communicate entirely by message
passing. It will be a beautifully clean architecture, but while Erlang may
have cheap concurrency, it does not have _free_ concurrency, and if you work
realistic math on the sheer number of messages you'd have flying around the
system it should become clear that it will not be practical to have literally
manifested "messages" in that quantity being continuously created and
destroyed.

If we tune our sights down from "AAA game", there are two possibilities. One
is you can create a less computationally-intensive game that can run Erlang on
the desktop. I suspect you'll find you're a bit short of libraries for that
use case, but with motivation you can pound through that. I'm not sure if this
has ever been done. The other thing you can use Erlang for is being the
backend server of a game system, and _that_ is eminently practical, in the
sense that it has been done: [http://erlang.2086793.n4.nabble.com/Erlang-Who-
uses-it-for-g...](http://erlang.2086793.n4.nabble.com/Erlang-Who-uses-it-for-
games-td2279186.html) You'd encounter some bumps if you tried to scale it up,
but that's not a very strong criticism since it's constant regardless of what
tech you'd end up using.

~~~
mercer
Thanks for the info. I was definitely not thinking of using Erlang/Elixir for
the actual game, but rather as a back-end. Glad to hear that wouldn't be a bad
choice.

------
sametmax
I don't see what's so good with that. Don't we have multiprocessing and
threading pools that does that stuff automatically ?

------
agentultra
Obligatory, _but Haskell_ , comment.

Nice post, looks like a decent API. I wonder how similar each implementation
of this model in Language X is and how easily it would be to test their
implementation meet the spec.

~~~
kccqzy
What about Haskell? Haskell has great support for writing code in the Actor
style if you wish. But you can also have traditional locks and simple mutable
pointers in Haskell too.

~~~
agentultra
It’s great for writing concurrent code that is both easy to understand and be
confident in its correctness.

I just didn’t want to derail the conversation away from D and the authors’
discovery.

------
rad_gruchalski
Try actors.

------
John_KZ
I'm not a professional programmer, but I had no problems with Java's
multithreading libraries. Lock handling, synchronization etc are a pain in the
ass to get used to but not so much of a big deal, and I don't see how D is any
different than that.

~~~
glic3rinu
With the actor model you don't share state between threads, hence no need to
lock anything.

Each variable can only be updated by a single and unique thread. If another
thread (or actor) wants to modify it, it has to send a modification request
messages to that one thread to do the job.

That's quite a difference!

Edit: Java threading model is fine for limited amounts of concurrency.
Complexity escalates quickly with highly concurrent applications; the actor
model makes reasoning about those programs far easier.

~~~
John_KZ
So it's like a lock, except you call it "modification request". Great, got it.

~~~
Jtsummers
The difference is that it doesn't halt your execution unless you want it to.
With shared memory and locks you have to wait when you set a value if anyone
else is interacting with that object or that part of the object (if you have
fine-grained locks). So you wait both for getting and setting.

If your system behavior does not _immediately_ depend on the value of what
you're changing, then actors are fantastic. You don't wait, it's async. You
send the data to the controlling actor (which is an object in the OO sense
itself). Your process continues on as if nothing changed _until_ you need the
updated results.

    
    
      Process A
        B ! {update, x, newVal},
        %% A bunch of work
        B ! {lookup, y, A}, % where y perhaps depended on x
        Y = receive % Here we finally wait, if B is processing a lot of requests
          {B,y,Val} -> Val
        end,
        %% more work
    
      Process B
      loop(State) ->
        receive
          {update, Var, Val} -> loop(update(State,Var,Val));
          {lookup, Var, Pid} -> Pid ! lookup(State, Var), loop(State)
        end.
    

Where B may be much more complex than that, in this case it looks like a
simple KV store.

The receive at the end of Process A is blocking and is the only time we end up
with a lock in Process A. So if A never needs to do anything but send values
to B, then A can run undisturbed by everyone else's data requests. This lends
itself well to certain pipelined and dataflow architectures.

