Threads in C# — Prevent Data Race using lock

Ghadeer Kenawi
6 min readJan 19, 2019

--

This article explains, with examples, the need for locking a block of code when it is overwhelmed by several threads simultaneously.

A multithread application has more than one thread; each thread is responsible for running a certain number of methods. The controller shares the running time between the threads, but the user generally does not have much control over which thread will start first nor how much time is taken before the controller starts the next thread. In a normal case of a multithread application, every thread starts, but before it’s done it gets interrupted by other threads then resumes its work again, but gets interrupted again... and so on until the thread finishes all its tasks and exits. This goes for every single thread.

If there is a method or a block of code which manipulates a global variable (shared resource), and if all threads can access the same block at the same time without protecting the variable then different values of the global variable appear every time the app runs. This is a result of Data Race, a condition which happens with this kind of behavior. Data Race condition gives no time to threads to inform other parts in the same application with the final value of shared resources which all thread are accessing at once which leads to incorrect outputs. Controlling this kind of behavior will give every thread enough time to change the value of the shared resource without allowing other threads to interrupt the process and at the same time gives enough time to inform other parts with the latest value of the shared resource.

To avoid Data Race, which leads to inconsistent results in a multithread application, lock the code where the global variable is expected to get hit by many threads simultaneously. This can be done using the lock keyword.

The following console application example shows the results and the behavior of threads; one time without using the lock, and the second time using the lock. The example will also show how Data Race occurs. The application uses a global integer variable, representing the shared resources between threads. It also uses a second global variable, type Object, which will be used to lock the chunk of code where the shared resources variable is manipulated. A Void method, PlusFive(), increments the shared resources variable from 0 to 5 each time it gets called.

Create a C# console application, name it ThreadLock.

Declare two global variables :

Create the PlusFive() method :

Now let's call the PlusFive() method three times, no threads are used here :

RESULTS:

The results above are expected; Adding 5s three times totals up to 15.

The code above is executed by only one thread, the main thread. Now let's create three threads. Each thread will call the PlusFive() method (no locking here), and the application must be run more than once or twice in order to observe if it will give the same result.(note: remember to add the Join() method for every thread in order for the main thread to wait until all threads are done).

RESULTS :

First time running: total = 15

Second time running:total = 13

Third time running: total = 12

Fourth time running: total = 15

Running the application four times, as seen above, with no locking is giving inconsistent results. In the first run, the total was 15, in the second run it was 13, in the third one12 and in the fourth run 15 . How did this happen? This is what we call Data Race. While one thread is changing the value of total, for example from 8 to 9 ,another thread is accessing same variable same time and trying to change it from 9 to 10 ,while doing this the first thread which is carrying a value of 8 went informing the rest of the application that the latest value of total is 8, missing all the changes done by the second thread. To demonstrate how Data Race happens, one can print out which thread is accessing the shared variable “total” and what value it incremented into. This will reveal how threads manipulate the total when the code block is not locked.

In the PlusFive() method, add this line:

Console.WriteLine(“Total is : “ + total + “ updated by “ + Thread.CurrentThread.Name);

Run the app,

RESULTS:

Results showing hoe Data Race occurs case not using lock

The results above show how Data Race occurs; It can be seen that t3 started first, it incremented <total> from 0 to 1 , but then t1 interrupted t3 and incremented <total> from 1 to 2 , when t2 interrupted t1 and incremented <total> from 2 to 3 ,then t3 is back and incremented <total> from 3 to 4.

This scenario will differ every time we run the application, and it will give us different results for <total>, once 15, another 13, 12 and so on.

Now, let us lock the block of code which increments the total, run the application and discuss the results.

To do this, the Object variable _lock must first be locked until the thread shared time is done. Then release the object, allowing the other thread to enter. Doing this will prevent threads from interrupting each other and protect total value. Overall, this will make the final result consistent every time the application is run.

for (int i = 1; i <= 5; i++)

{

lock (_lock)

{

total++;

}

}

Comment out the code in line 44 (we will use it later ) as for this step we want to see if the final result of total if consistent.

Run the application:

RESULTS:

First time running:total =15

Second time running: total=15

Third time running: total=15

Fourth time running: total=15

From the results above, we can see that the value of the total is always 15 every time the application is run

Now let us uncomments line 44 to watch the thread’s behavior with the object locked :

RESULTS:

Results of No Data Race occur when using lock

From the results above, it is observed that every thread is able to access the variable total, and made this change with no interruption from other threads.

Locking shared resource in multithread applications ensures results consistency.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Other articles by Ghadeer Kenawi :

Threads in C# — Part1: Single-threaded vs Multi-threaded applications

Threads in C# -Part2- Behind the Scene — ParameterizedThreadStart

Reflection in C# — How to load assembly and retrieve its metadata using Reflection

C#-Fluent Interfaces for Unit Testing

Solving Technical Mysteries: A Step-by-Step Guide to Being a Successful Software Support Engineer

Resources:

Threads and threading | Microsoft Docs

lock statement — C# Reference | Microsoft Docs

--

--

Ghadeer Kenawi

Professional Software Support Engineer at : IBMWatson ,UnitedHealthGroup ,Generac , IMC & more . LinkedIn : https://www.linkedin.com/in/ghadeer-kenawi-b480b311/