August 14, 2013

.Net Threading

Guys at work told me to stop being a node hippy and write some more .net articles. Luckily, I have been working with threading and since it always is a difficult subject, I thought I would write about some experiences I have had with it.

So, the idea of threads are pretty simple, I do some stuff on different threads and cause parallel work to be created. This allows us to utilize multiple cores, which makes things faster. But, what most forget is that threads, on a single CPU, were actually created to cause responsive and reliable systems. If you used older versions of Windows, like 3.1, you will notice how most processes had the ability to freeze windows, lock it up and cause windows to halt to a crawl. So, how did all of this cause Windows to create threads the way they did?

Easy, they separated out memory of a process from the memory of the OS. This made the OS much more stable, and responsive. If the process crashes, then the OS can keep going and the user doesn’t lose everything. The problem with this, and the problem we still have today, is that threads are the biggest memory hog ever. Now, it’s not that bad, but you definitely don’t want to spin up 100’s of these things. So, in comes ThreadPool. Let’s take a look at some ThreadPools to get a better idea of how to use it.

 class Program { static readonly Semaphore s = new Semaphore(4, 4); static void Main(string[] args) { int i = 0; while (true) { if (i == int.MaxValue) { i = 0; } s.WaitOne(); ThreadPool.QueueUserWorkItem(work, i++); } } static void work(object state) { var d = new dodo(); d.work(state); s.Release(); } } public class dodo { public void work(object state) { Console.WriteLine("Doing work at '{0}' state: '{1}'", DateTime.Now, state); Random r = new Random(); Thread.Sleep(r.Next(1, 3)*1000); } } 

So, we have a few things here to take into account, let me enumerate them and then discuss them one by one. These are the interesting things for threading:

  1. Semaphore(4,4)
  2. ThreadPool.QueueUserWorkItem
  3. s.WaitOne()
  4. s.Release()

So, #1, we have a create of a Semaphore. It is used to control how threads work, by blocking processing, based on a number. Typical use here is to say we are starting with 4, and the cap is 4. In the program, it will queue up 4 requests for threads and stop. This is very helpful to controlling the while loop, as if we did not have the semaphore there, we would have created too many requests for threads and crashed the program. You could use Environment.ProcessCount to find out how many cores your computer has to spin up threads. This plus a few more, may be a better metric to use, this is because as you create a request for a thread, it puts it in a queue for work. You have to be careful, though, because if you create too many requests, it will run out of memory and crash.

Next, ThreadPool.QueueUserWorkItem, this function takes in a function (usually static) and does that work on the thread. As the requests come in, .net figures out when and how many threads to spin up to do the work. When there are no more jobs to process, the threads will be destroyed. The reason you may want to spin up more threads than Environment.ProcessCount is to force .net to recognize that it should use more threads. All of this is a bit tricky and may not fit EVERY architecture. Be sure to test it out for your particular situation.

#3 and #4 are basically used for controlling if another thread can continue. It is just like using lock and monitor, but unlike those mechanism, Semaphores keep track of a number of threads, so you can spin up multiple threads and control more than just one thread.

ThreadPools are VERY powerful, but there is one caveat, if the process closes, then the treads are told to die instantly. You can control this to an extent, by setting the Thread.CurrentThread.IsBackground to false. A foreground thread is used for critical processing points, like database writes. Take care, because these will leave a process hanging open.

Remember, use threads to keep your application responsive and reliable. In fact, async and await in .net 4.5 make it easy to do this for your UI threads. If you are doing this in services, and backend, you can use ThreadPools, Semaphores and other locking structures to keep your application synced. Foreground and background threads help keep your application from killing a critical process in your app, but need to be carefully considered so that it does not leave your app hanging out forever. All of these things will help you respect threading MUCH, MUCH more.