I was recently reading a post about writing non-threadsafe code which talks about the main peril of multi-threading, and one way you can work around it.
I’ve long been a believer that doing anything multi-threaded is fraught with danger and you have to tread incredibly carefully when doing so. I say this with experience. What I learnt from reading that post wasn’t in the content, but in the comments, which talked about the Interlocked class for performing simple, thread-safe increments and decrements of operators.
So i decided to try it and see what the benefit really is, and i was surprised by by the results! I did my own profile against 3 scenarios:
- No thread safety (fast comparitively, though gave incorrect results)
- Locking using “lock” keyword (correct, but very slow by magnitude of nearly 10x)
- Locking using Interlocked class (correct, and fast – faster than no thread safety in some test runs)
Clearly these results aren’t scientific, but are quite good to give relative indicators of performance. I’ve reproduced the code below.
using System; using System.Diagnostics; using System.Threading; using NUnit.Framework; namespace ThreadingExample { public interface IThreadTest { int Value { get; } void Debit(); void Credit(); } public class NonThreadSafe : IThreadTest { public int Value { get; private set; } public void Debit() { Value--; } public void Credit() { Value++; } } public class ThreadSafe : IThreadTest { public int Value { get; private set; } object lockSentinel = new object(); public void Debit() { lock (lockSentinel) { Value--; } } public void Credit() { lock (lockSentinel) { Value++; } } } public class ThreadSafeUsingInterlocking : IThreadTest { private int value; public int Value { get { return value; } private set { this.value = value; } } public void Debit() { Interlocked.Decrement(ref value); } public void Credit() { Interlocked.Increment(ref value); } } [TestFixture] public class TestClass { [Test] public void TestNonThreadSafe() { NonThreadSafe nts = new NonThreadSafe(); ExecuteThreadedTest(nts); Assert.AreEqual(0, nts.Value); } [Test] public void TestThreadSafe() { ThreadSafe ts = new ThreadSafe(); ExecuteThreadedTest(ts); Assert.AreEqual(0, ts.Value); } [Test] public void TestThreadSafeUsingInterlocking() { ThreadSafeUsingInterlocking tsui = new ThreadSafeUsingInterlocking(); ExecuteThreadedTest(tsui); Assert.AreEqual(0, tsui.Value); } private void ExecuteThreadedTest(IThreadTest threadTest) { int maxIterations = 99999999; DateTime start = DateTime.Now; Thread t1 = new Thread(() => { for (int i = 0; i < maxIterations; i++) { threadTest.Credit(); } } ); t1.Name = "t1"; Thread t2 = new Thread(() => { for (int i = 0; i < maxIterations; i++) { threadTest.Debit(); } } ); t2.Name = "t2"; t1.Start(); t2.Start(); t1.Join(); t2.Join(); DateTime finish = DateTime.Now; Debug.WriteLine(String.Format("Took {0}ms to complete", (finish - start).TotalMilliseconds)); } } }