0% found this document useful (0 votes)
29 views

Understanding and Avoiding Race Conditions in Multithreaded C# Applications - Pluralsight

Uploaded by

sestidavide.mail
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
29 views

Understanding and Avoiding Race Conditions in Multithreaded C# Applications - Pluralsight

Uploaded by

sestidavide.mail
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

7/5/2019 Understanding and Avoiding Race Conditions in Multithreaded C# Applications | Pluralsight

A Naive Approach to Data Access Synchronization


4 Get started Log in

When to Worry About Race Conditions


Understanding and Avoiding Race
The Case for Synchronizing Access to Data
A Naive Approach to Data Access Synchronization

Top
Nate Cook Conditions in Multithreaded C#
The Correct Way to Synchronize Access

Applications
Nate Cook
Nov 19, 2018 • 8 Min read • 884 Views

Nov 19, 2018 • 8 Min read • 884 Views

C# Applications

When to Worry About Race Conditions

In modern applications, it is common to have more than one sequence


of instructions executing at any given moment. These sequences of
instructions are known as threads. All but the simplest of applications
have multiple threads, so it's important to understand what can
happen in a multithreaded application at run-time.

In some cases, a developer may only need to worry about a single


thread, even though the application is multithreaded. For example, in
.NET garbage collection happens on a separate thread, but the
developer may not need to give much consideration to that fact. It is
quite common however for a developer to initiate his or her own

https://www.pluralsight.com/guides/race-conditions-multithreaded-csharp 1/8
7/5/2019 Understanding and Avoiding Race Conditions in Multithreaded C# Applications | Pluralsight

threads,
A Naive Approach to Data Access to perform some work "in the background", as it were. It is
Synchronization
4these cases where race conditions most often appear.

As you might have guessed, a race condition is not something a


4
developer codes or explicitly permits. Rather it is something that can
When to Worry About Race Conditions
happen
The Case for Synchronizing into
Access a multithreaded
Data application that does not have proper
A Naive Approach to Datasafeguards.
Access Synchronization
Most commonly, preventing race conditions requires
The Correct Way to Synchronize Access
synchronizing access to data that occurs from multiple threads.
Top

The Case for Synchronizing Access to Data


To understand the need for data synchronization, let's look at an
example: Say you are writing a web crawler console application that
downloads the HTML for a particular URL and writes the links (e.g. <a
href="/path/to... ) that it finds to a file (e.g. links.txt ). In true
web crawler style, the application then downloads the HTML for each
of those links, and continues recursively until some limit is reached, or
until the HTML for all links have been retrieved/processed.

To do so synchronously would be quite slow because the application


would have to wait for the HTML of one link to finish downloading
before it even starts the request for the next one. So to speed things
up, you decide to do it asynchronously by utilizing a separate thread
for each link request. A simple implementation of such a web crawler
might look like the following:

csharp
1 const int MaxLinks = 8000;
2 const int MaxThreadCount = 10;
3 string[] links;
4 int iteration = 0;
https://www.pluralsight.com/guides/race-conditions-multithreaded-csharp 2/8
7/5/2019 Understanding and Avoiding Race Conditions in Multithreaded C# Applications | Pluralsight

5 Synchronization
A Naive Approach to Data Access
4
6 // Start with a single URL (a Wikipedia page, in this exam
7 AddLinksForUrl("https://en.wikipedia.org/wiki/Web_crawler"
8
4
9 while ((links = File.ReadAllLines("links.txt")).Length < M
When to Worry About Race Conditions
10 {
The Case for Synchronizing Access to Data
11 int offset = (iteration * MaxThreadCount);
A Naive Approach to Data Access Synchronization
12
The Correct Way to Synchronize Access
Top 13 var tasks = new List<Task>();
14 for (int i = 0; i < MaxThreadCount && (offset + i) < lin
15 {
16 tasks.Add(Task.Run(() => AddLinksForUrl(links[offset +
17 }
18 Task.WaitAll(tasks.ToArray());
19
20 iteration++;
21 }

Where AddLinksForUrl looks something like:

csharp
1 static void AddLinksForUrl(string url)
2 {
3 string html = /* retrieve the html for said url */ ;
4 List<string> links = /* extract the links from the html
5
6 using (var fileStream = new FileStream("links.txt",
7 FileMode.OpenOrCreate, FileAccess.ReadWrite, File
8 {
9 List<string> existingLinks = /* read the file contents
10 foreach (var link in links.Except(existingLinks))
11 {
12 fileStream.Write(/* the link URL, as bytes, plus a n
13 }
https://www.pluralsight.com/guides/race-conditions-multithreaded-csharp 3/8
7/5/2019 Understanding and Avoiding Race Conditions in Multithreaded C# Applications | Pluralsight

14 Synchronization
A Naive Approach to Data Access }
4
15 }

When to Worry About Race The key point to note in the main algorithm is that a new thread is
Conditions
being
The Case for Synchronizing initiated
Access with each call to Task.Run . Since we defined a
to Data
A Naive Approach to DataMaxThreadCount
Access Synchronization
of ten, ten threads would be initiated, then
The Correct Way to Synchronize Access
Task.WaitAll would wait until the work in all of those threads
Top
completed. After that, a new batch of threads is initiated in the next
iteration of the while loop.

Fully implemented, this web crawler may actually work fine. But if you
run it enough times, you'll eventually get an IOException . Why is
that?

System.IO.IOException: The process cannot access the file


'/path/to/links.txt' because it is being used by another process.

Notice in AddLinksForUrl that we use FileShare.None to obtain


exclusive access to links.txt . And rightly so, since multiple
processes writing to the same file simultaneously can cause problems,
including data corruption. Depending on when the web servers
respond and how long it takes to download the HTML, from time to
time our web crawler may have more than one thread attempting to
open links.txt at exactly the same time. We, therefore, need to
synchronize access to the links.txt file, such that it never occurs
from more than one thread simultaneously. Such synchronization is
needed for any data shared between threads.

A Naive Approach to Data Access Synchronization


https://www.pluralsight.com/guides/race-conditions-multithreaded-csharp 4/8
7/5/2019 Understanding and Avoiding Race Conditions in Multithreaded C# Applications | Pluralsight

A Naive Approach to Data Access Synchronization


Consider for a moment the most straightforward attempt at
4synchronizing access to shared data—a boolean flag. We could simply
set a flag to true when we open the file, set it to false when we're
4 done, and check the flag before we attempt to open the file. That
When to Worry About Race Conditions
ought to do the trick, right?
The Case for Synchronizing Access to Data
A Naive Approach to Data Access Synchronization csharp
The Correct Way to Synchronize
1 Access
static bool fileIsInUse;
Top 2
3 static void AddLinksForUrl(string url)
4 {
5 ...
6
7 while (fileIsInUse)
8 {
9 System.Threading.Thread.Sleep(50);
10 }
11
12 try
13 {
14 fileIsInUse = true;
15
16 using (FileStream fileStream = new FileStream("links.t
17 {
18 ...
19 }
20 }
21 finally
22 {
23 fileIsInUse = false;
24 }
25 }

https://www.pluralsight.com/guides/race-conditions-multithreaded-csharp 5/8
7/5/2019 Understanding and Avoiding Race Conditions in Multithreaded C# Applications | Pluralsight

Actually
A Naive Approach to Data Access yes, that approach may synchronize access to the links file to
Synchronization
4a certain extent. But run it enough times and eventually you will get
another IOException . Essentially, the same problem still exists, but
4 why?
When to Worry About Race Conditions
Remember
The Case for Synchronizing Access tothat
Datawe have multiple threads executing the code in
A Naive Approach to DataAddLinksForUrl
Access Synchronization
simultaneously. The mistake we are making with the
The Correct Way to Synchronize Access
naive approach is that we are not guaranteeing that only a single
Top
thread sets the fileIsInUse flag to true at a time. So, in the moment
that fileIsInUse is set to false in the finally block, multiple
threads may be waiting in the while loop above. If more than one
thread breaks out of the while loop at the same (or almost the
same) time while fileIsInUse is false, they will all enter the try
block, and they will all think they have exclusive access to the file. In
that situation, the IOException will occur. Such an anomaly is an
example of a race condition.

Race conditions can be especially insidious because of the fact that


the compiler translates a single C# instruction to multiple machine
level instructions. That means that what appear to be back to back
lines of code in C# may actually be separated by quite a few
instructions in the corresponding machine code. The actual order of
execution across threads at run-time may not match what we
intended if we do not set guarantees for critical sections of our code.
In short, when the order matters, we can't leave it to chance. And for
shared data, any time a thread needs exclusive access, we need to
guarantee such exclusive access.

The last thing we learn from the failure of the naive approach is that
"shared data" in the context of multiple threads does not only refer to
files. No, in fact it refers to anything shared across threads, which

https://www.pluralsight.com/guides/race-conditions-multithreaded-csharp 6/8
7/5/2019 Understanding and Avoiding Race Conditions in Multithreaded C# Applications | Pluralsight

includes
A Naive Approach to Data Access variables—be they value types such as the boolean in the
Synchronization
4example above, or reference types.

When to Worry About Race Conditions


The
The Case for Synchronizing Correct
Access to DataWay to Synchronize Access
A Naive Approach to Data Access Synchronization
The Correct Way to Synchronize Access
Now that we know we need to guarantee synchronous access to
Top shared data in multithreaded applications (in order to, among other
things, avoid race conditions), how do we actually accomplish that?
Well, C# and the .NET Framework provide a number of ways to do
that, but one of the easiest and most common ways is to use the
lock statement. The next guide in this series will explore the lock
statement in detail.

Test your skills. Learn something new. Get help. Repeat. Start a FREE 10-day trial

ABOUT SOLUTIONS PLATFORM SUPPORT COMPANY

Business Browse library Help center Partners

Personal Role IQ Integrations PluralsightOne.org


CONTACT
Small business Skill IQ IP whitelist Customer stories

Academic Iris Careers

Federal government Paths Teach


EVENTS State & local government Projects Blog

Interactive Courses Newsroom

Guides Pluralsight engineering

Authors Affiliate program

https://www.pluralsight.com/guides/race-conditions-multithreaded-csharp 7/8
7/5/2019 Understanding and Avoiding Race Conditions in Multithreaded C# Applications | Pluralsight

A Naive Approach to Data Access Synchronization


Mobile apps Subscribe

4 Professional Services

When to Worry About Race Conditions


The Case for Synchronizing Access to Data
A Naive Approach to Data Access Synchronization
Site Map | Terms of Use | Privacy Policy Copyright © 2004 - 2018 Pluralsight LLC. All rights reserved
The Correct Way to Synchronize Access
Top

https://www.pluralsight.com/guides/race-conditions-multithreaded-csharp 8/8

You might also like