Multi threaded design guidelines for librariesPart II
Next on the agenda for writing correct multi threaded libraries, how do you handle shared state?
The easiest way to handle that is to use the same approach that NHibernate and the RavenDB Client API uses. You have a factory / builder / fizzy object that you use to construct all of your state, this is done on a single thread, and then you call a method that effectively “freeze” this state from now on.
All future accesses to this state are read only. This is really good for doing things like reflection lookups, loading configuration, etc.
But what happens when you actually need shared mutable state? A common example is a cache, or global statistics. This is where you actually need to pull out your copy of Concurrent Programming on Windows and very carefully write true multi threaded code.
It is over a thousand pages, you say? Sure, and you need to know all of this crap to get multi threading working properly. Multi threading is scary, hard and should not be used.
In general, even if you actually need to do shared mutable state, you really want to make sure that there are clear definitions between things that can be shared among multiple threads and the things that cannot. And you want to make most of the work in the parts where you don’t have to worry about multi threading.
It also means that your users have much easier time figuring out what the expected behavior of the system is. This is very important with the advent of C# 5.0, since async API are going to be a lot more common. Sure, you use the underlying async primitives, but did you consider what may happen when you are issuing multiple concurrent async requests. Is that allowed?
With C# 5.0, you can usually treat async code as if it was single threaded, but that breaks down if you are allowing multiple concurrent async operations.
In RavenDB and NHibernate, we use the notion of Document Store / Session Factory – which are created once, safe for multi threading and are usually singletons. And then we have the notion of sessions, which are single threaded, easy & cheap to create and follow the notion of one per thread (actually, one per work unit, but that is beside the point).
On my next post, I’ll discuss what happens when your library actually wants to go beyond just being safe for multi threading, when the library wants to use threading directly.
Comments
I'm working on a system based around a shared mutable in-memory datastore (persistence achieved via an eventstore which logs all method calls), as opposed to a DB.
Do you have any thoughts on the best way to achieve thread safety on a system like this? I am using a lazy ReaderWriterLockSlim on begin/end request... it makes me feel slightly icky.
Instead of "Concurrent Programming on Windows" I would first encourage to read the chapters on "Concurreny" in "Clean Code". Do not be afraid to follow the usual MS way: Static methods are thread-safe, instance methods are not thread-safe.
@Harry McIntyre if you can't get rid of all of your usages of locks for that, and instead replace them all with interlocked calls instead. I would run for the hills.
Comment preview