C# Design Pattern : Singleton
The singleton pattern basically involves a class that only allows a single instance of itself to be instantiated.
The first snippet gives a basic (but bad) idea on how to create a singleton.
public sealed class Singleton { static Singleton _instance = null; private Singleton() { Console.WriteLine("Instance Created"); } public static Singleton Instance { get { if (_instance == null) { _instance = new Singleton(); } return _instance; } } }
A few notes on the previous snippet:
- Seal this class in order to prevent derived classes to break the pattern.
- Create a private constructor in order to prevent instantiation of the class.
- Return an instance of the class via static property.
There is however a fundamental problem with the preceding snippet which become apparent as soon as we introduce threading into the equation, observe:
class Program { static void Main() { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new ThreadStart(ThreadMain)); thread.Start(); } } static void ThreadMain() { Thread.Sleep(500); try { Singleton s = Singleton.Instance; } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
As soon as we use the singleton (in the first snippet) within multiple threads, you will notice multiple objects being instantiated e.g. "Instance Created" gets written to the console multiple times - in the case of a real singleton its supposed to only write to the console once.
In the following snippet we solve the threading issue by using a lock:
public sealed class Singleton { private static volatile Singleton instance; private static object locker = new Object(); private Singleton() { Console.WriteLine("Instance Created"); } public static Singleton Instance { get { if (instance == null) { lock (locker) { if (instance == null) instance = new Singleton(); } } return instance; } } }
By running the preceding snippet in the threading snippet, you will notice only one "Instance Created" message.
A few notes on the previous snippet:
- Use volatile in order to ensure that assignment to the variable completes before we access it.
It is however possible to achieve thread-safety without a lock, by relying on the CLR (common language runtime) to handle initialization - the following method is the preferred approach to creating a Singleton in .NET.
public sealed class Singleton { public static readonly Singleton Instance = new Singleton(); static Singleton() { } private Singleton() { Console.WriteLine("Instance Created"); } }
Few notes:
- Set the static instance variable to readonly in order to prevent any changes to be made to the instance as soon as its created.
- Add a static constructor with regards to lazy initialization e.g. beforefieldinit flag. (the static constructor initializes as soon as we access a static member - which in this case postpone the creation of the singleton instance)
Terminology:
Lazy Initialization
Refers to a performance optimization that postpones the need to create/instantiate an object until its really needed - by contrast the default initialization for objects in .NET is called eager initialization - happens immediately.
Note that all of the Singleton snippets in this post make use of lazy initialization.
In the .NET 4.0 framework Microsoft added the Lazy Class to provide developers with a simple way to use lazy initialization in their code, observe the following .NET 4.0 Singleton:
public sealed class Singleton { private static readonly Lazy<Singleton> _instance = new Lazy<Singleton>(() => new Singleton()); private Singleton() { Console.WriteLine("Instance Created"); } public static Singleton Instance { get { return _instance.Value; } } }
Additional Reading:
Interesting article written by Jon Skeet
http://csharpindepth.com/Articles/General/Singleton.aspx
Microsoft article regarding the singleton pattern
http://msdn.microsoft.com/en-us/library/ff650316.aspx
Posted by - Christoff Truter
Date - 2010-08-10 22:52:52
Comments - 4
Date - 2010-08-10 22:52:52
Comments - 4
C# Threading: Mutual exclusion (via Monitor Class)
Observe the following faulty snippet from this post:
using System; using System.Threading; using System.IO; class Program { static void Main(string[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new ThreadStart(ThreadMain)); thread.Name = String.Concat("Thread - ", i); thread.Start(); } } static void ThreadMain() { // Simulate Some work Thread.Sleep(500); // Access a shared resource / critical section WriteToFile(); } static void WriteToFile() { String ThreadName = Thread.CurrentThread.Name; Console.WriteLine("{0} using resource", ThreadName); try { using (StreamWriter sw = new StreamWriter("1.txt", true)) { sw.WriteLine(ThreadName); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
Like earlier explained, what we've got here is three threads trying to write to a file, which fails horribly since we cant concurrently write to the same file.
Previously we used the Mutex class to resolve this issue, but we won't always need the power of the Mutex class (by which we can achieve a global lock on resources within our processes) - a lot (if not most) of the time the Monitor class will suffice.
Observe the monitor based solution:
static object locker = new object(); static void WriteToFile() { String ThreadName = Thread.CurrentThread.Name; Console.WriteLine("{0} using resource", ThreadName); Monitor.Enter(locker); try { using (StreamWriter sw = new StreamWriter("1.txt", true)) { sw.WriteLine(ThreadName); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { Monitor.Exit(locker); Console.WriteLine("{0} releasing resource", ThreadName); } }
Basically each thread will wait until the locker object gets released by the first thread that achieved a lock, before it attempts to write to our file.
Alternatively we can simply make use of the lock statement, which neatly wraps the Monitor.Enter - Monitor.Exit statements like this:
static object locker = new object(); static void WriteToFile() { String ThreadName = Thread.CurrentThread.Name; Console.WriteLine("{0} using resource", ThreadName); lock (locker) { try { using (StreamWriter sw = new StreamWriter("1.txt", true)) { sw.WriteLine(ThreadName); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
We do however have more control (if needed) over our lock using the Monitor (instead of the lock statement), like visible in the next crude snippet:
if (Monitor.TryEnter(locker, 500)) { try { Console.WriteLine("Lock Achieved"); Thread.Sleep(1000); // Simulate work } catch { throw; } finally { Monitor.Exit(locker); } } else { Console.WriteLine("Lock Failed"); }
If a thread can't achieve a lock within our defined 500 milliseconds, we've got the ability to handle it - instead of just waiting forever (or whenever garbage collection eventually happens).
Note:
Generally fields that are read/written within multiple threads, should be read/written within a lock.
Vocabulary
Atomicity / Atomically
From the greek word "atomos" which means indivisible (early scientists prematurely named the atom after this word - which they used to consider to be the smallest building block of matter).
In context of thread locking and atomicity - If variables are only read/written within the same exclusive lock, it means that we isolate these variables to a specific thread, outside threads (concurrent processes) can't alter these variables - these variables are read/written atomically.
Additional reading:
C# Threading: Mutual exclusion (via Mutex Class)
http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.71).aspx
http://msdn.microsoft.com/en-us/library/aa664735(VS.71).aspx
Note: Additional overloads added to .net 4.0
http://msdn.microsoft.com/en-us/library/system.threading.monitor.tryenter.aspx
Threading Guidelines:
http://msdn.microsoft.com/en-us/library/f857xew0%28VS.71%29.aspx
Posted by - Christoff Truter
Date - 2010-08-06 15:31:15
Comments - 0
Date - 2010-08-06 15:31:15
Comments - 0
First 16 17 18 19 20 21 22 23 24 25 Last / 62 Pages (124 Entries)