By: Jeremy Holley
Multithreading is one of the more advanced ways of speeding up your applications. Most every developer will have to write a concurrent application sooner or later.
In this part I will cover the basic techniques for multithreading such as signaling and accessing shared resources.
Threading poses us with unique issues regarding the access of shared resources (e.g. variables, lists, etc.). You can have multiple threads trying to modify the same resource which can lead to unpredictable results.
To prevent threads from accessing the same resource at the same time we can use constructions such as the semaphores or monitors. In C# the monitor implementation is the lock statement. To implement it you need to create a locker object which has to be locked before the shared resourced is accessed and then unlocked afterwards. Once the lock object is locked other threads have to wait until the object is unlocked. Exampled Below
The bool in the above example isDone is a shared resource between threads. The example above has 2 threads the main thread and then the one that is invoked from the main thread. Both will try to access IsDone. Looking at method proc execution we can see that isDone is accessed twice. Once to check its value and once to set its value if it was false. The issue here is the main thread may access isDone first while its value is false then give up processing time to thread 2 which will also see isDone as false and the console will respond with “done” twice. That is behavior we do not want. The solution for this issue is to use a lock object.
Now the if statement is secured by a lock which forces every thread that wants to enter it to obtain the lock. Once a lock is obtained by a thread another thread can not proceed. It will wait until the lock is freed. Above, when the main thread has the lock it will execute the if statement before handing processing back to the second thread.
Another technique to use is signaling. Signaling is notifying other threads. One example is a thread can invoke another thread then wait for the invoked thread to signal completion. Thread 2 would be performing some operation while thread 1 waits. One thread 2 completes its task it sends a signal back to thread 1 which then continues on.
Below the main thread creates a new thread to perform thread2Operation. After that it performs an operation then waits for thread 2 to signal that it is finished. Once that signal is received thread 1 proceeds.
The basic implementation of multithreading in an application is the Producer-Consumer which is widely used in many real-life applications. Below is an example:
Multithreading offers many advantages such as faster applications or a responsive UI. It does however make it more difficult to debug and can introduce additional issues. The advantages do not always outweigh the additional complexity that it introduces. However, being familiar with what it can and cannot do is part of becoming a master developer.
Threading in C# by Joseph Albahari (http://www.albahari.com/threading/)
Threading in .NET by Jon Skeet (http://jonskeet.uk/csharp/threads/)