It looks like my last post about threading has gotten around a bit. Len Holgate linked to it, for example, and following various links from there eventually led me to something by Herb Sutter that I want to comment on. He attributes the following quote to Iain McInnes, but I can’t find the original context.

Don’t share data between threads. Just don’t
Easy to say: People didn’t like giving up GOTO, either.
What’s the alternative ? Asynchronous message exchange.

I don’t think that’s exactly bad advice, but it’s a little bit too limiting. Contra Herb, I don’t believe shared state is inherently evil; it’s all a matter of how you use it. Simply leaving state (by which I mean data within a single address space) lying around in scattereed pieces, with arbitrary sections of code accessing it at their convenience (even with “correct” locking), is evil. However, simply not sharing state is a degenerate way of avoiding the problem, just like not having more than one thread is a degenerate way of avoiding concurrency problems in general. For one thing, those asynchronous message queues themselves represent a kind of shared state, with appropriate locking. It suffices for a great many purposes, but there are also many situations where trying to fit a program’s natural synchronization to that model is a bad idea. The analogy I’d make is not to GOTO but to abstract data types and object-oriented program, and the injunction I’d offer as an alternative is this (click through to read beyond the quote):

Share data between threads only in constrained high-level ways.

The code that shares data between threads should be encapsulated in a few places that define design-level operations on that shared data, just as the code that modifies data within an object should be encapsulated within methods on that object. In fact, if you’re programming in an object-oriented language, the data-sharing code might not just be analogous to methods on objects; it will probably be implemented as methods on objects. The “access from anywhere” evil I mention above is exactly like making all class members public and letting everyone just bang on them wherever, but the analogy extends even further. With objects, it’s possible to adhere to the letter of the “use accessor functions” dogma without serving any useful purpose, by just writing trivial get/set accessors and using those indiscriminately to get all of the bad coupling of using direct member access except that now it’s less efficient too. Similarly, it’s possible to satisfy Herb/Iain’s injunction without serving any useful purpose by sharing data via messages without actually changing the control flow that’s the real problem. Again, you still have the original problem and now it’s inefficient as well. You can’t just implement the Same Old Thing differently; you actually have to rethink how you’re doing it to get any real benefit. Figure out what high-level operations you actually need on shared data and express the rest of your code in those terms, instead of just leaving everything alone and adding a layer of message-passing glop.

There’s another problem with the asynchronous-message-passing religion that’s also worth mentioning, and that’s a data consistency problem. If there’s one problem that I’ve dealt with in a career that spans what most would regard as multiple specialties, and which I’ve gained some notoriety for dealing with effectively, it’s data consistency. In a nutshell, any time you make copies of data without having an explicit policy for how to maintain consistency between those copies (where “let them diverge” is an acceptable policy if it’s consciously chosen), you risk falling prey to consistency problems. Messaging generally involves making copies; if it doesn’t, you probably have a more complex state-sharing problem than before as you have to maintain reference counts, do copy-on-write, etc. Therefore, if you just blindly follow the advice to use asynchronous messaging instead of sharing state between threads, you might simply have traded one set of race conditions and potential deadlocks for an even harder to debug set of consistency problems. Code that communicates via messages is sharing state too. It’s just implicit rather than explicit, which is bad for reasons I explained in a previous entry. It still has to be managed somehow. If your design makes the message itself the authoritative source for any data it contains, and the message contents are properly protected against loss (which can actually be non-trivial if what you’re implementing is supposed to be highly available), then you’re probably fine, but many designs don’t work that way. At least as often, the authoritative copies reside elsewhere and get updated upon receipt of a message, and then you very much have to worry about keeping the recipient’s state consistent with the sender’s. There are reasonable ways to deal with this, familiar to many from the world of networking and distributed systems in which the sender and receiver do not share an address space, but it’s a fundamentally different paradigm than that commonly used within a multi-threaded program. People who naively switch to a pure message-based model within such a program usually end up with lots of extra data copies, lots of extra context switches, and terrible performance in return for little or no gain in correctness and maintainability.