2 Person Games is a one person independent games studio, founded in May 2011. Being a one person studio I do everything for the company – that includes design, development, artwork (usually outsourced to friends), sounds (usually outsourced to other friends) and marketing (usually failing at). I occasionally take on freelance work when I can to give me room to think and create. I’m passionate about games of all sorts. I love video games, boardgames, tabletop and live-action roleplaying games. My favourite boardgame of all time is Twilight Imperium, without a shadow of a doubt. My favourite video game is StarCraft. As you can tell, there’s a strong running theme of Science Fiction there, which explains Space Salvager. My passion in life is making games, that’s what I do. Be it games mastering for my RPG group or making video games for a game jam – making games is what I do. I’ll develop for any platform that’s appropriate, which is usually desktop and any console that’ll take me.
Posted by 2PersonGames on Feb 13th, 2014
The purpose of this post is to give an introduction to making thread-safe calls specifically within .NET/Mono.
A thread safe calls are a type of call that can be made from any thread that won’t interfere with the operations of another thread. This is an important overhead to include because, as an example, if you have one thread reading a list and another thread writing to that list at the same time the first thread is most likely going to go beyond the bounds of the list. One of the ways to achieve this in a thread-safe environment is to control access by using either a lock, mutex or semaphore.
The way a lock, mutex and semaphore work is to block the calling thread until they have been released. For .NET/Mono there is very little difference between a lock and a mutex; a semaphore on the other day is essentially a more complicated lock, allowing for multiple threads to read/write. Most of the you’ll use a mutex, but sometimes you may want to control read and writes in a far more complex way in which case you should use a semaphore.
The way mutexes work is you enter the mutex before accessing the memory in question, perform your operations, then exit the mutex after you’ve completed. However, the best one I have found for using across .NET and Mono is a semaphore called ReaderWriterLockSlim:
The above TryEnterWriteLock() will attempt to enter the semaphore for an infinite number of milliseconds (-1). This is a fairly efficient and fast sempahore that supports both read and write locks, with multiple and combined cases for both. Personally I use it as a straight mutex instead of a SpinLock because at the time of porting Space Salvager it was not available in Mono.
When executing code that requires a mutex it’s fairly standard to run that inside a try and finally statement. What this ensures is that any synchronisation exceptions are caught and, in the slim but often frequent chance, any exception is thrown the mutex is always released. The below example shows the base object GameObject in Space Salvager handling it’s update call using a mutex:
All objects inherit from this abstract class, overriding Update to implement their own update code. Because the call is made from within Tick all the calls made within Update to itself are thread-safe. If another object is accessed, you would call Lock on that object to ensure synchronisation. The issue to bare in mind, however, is not to cause a recursive lock. If a thread enters a mutex a second time before exiting this is known as a recursive lock. Some mutexes will allow this, for example a Monitor will, however the issue with this is when the subsequent exit is called the mutex is immediately released before it is expected to have been.
The solution to a recursive lock is simply to plan your architecture and ensure you don’t lock your mutex twice on the same thread. Sometimes this is very easy, but if you’re making a rather complex game then it can become very tricky.
Sometimes it isn’t necessary to use a mutex to access an object atomically. .NET/Mono implements an atomic class called Interlocked, which allows integers and other numerical value types to be read and written to atomically.
This is good when using an index or an ID number for example, to be honest however if you need to use an Interlocked you’ll know when.