I am new to threading. So, I just created 4 dedicated threads (rather using Tasks) one after other, and giving them something to work. But, It made me a doubt about race condition in startProcessing function where I am simply storing the value on currentIndex to a local variable within lock. However, I ran it so many times, but could not satisfy that there is no race issue or anything else.
Please help me to clear this thing.
class Program
{
private static object syncLock = new object();
int num = 0;
int currentIndex = 0;
static void Main(string[] args)
{
Program p = new Program();
p.num = 1000;
p.callThreadCreation();
while (p.num > 0)
{
Thread.Sleep(1000);
}
Console.WriteLine("All done");
}
public void callThreadCreation()
{
for (int i = 1; i <= 4; i++)
{
string name = "T" + i;
Thread T = new Thread(new ThreadStart(() => startProcessing()));
T.Name = name;
T.Start();
}
}
**private void startProcessing()
{
while (num > 0)
{
int tempIndex;
lock (syncLock)
{
tempIndex = currentIndex;
}
Interlocked.Decrement(ref num);
Interlocked.Increment(ref currentIndex);
print(tempIndex);
Thread.Sleep(1000);
}
}**
private void print(int x)
{
Console.WriteLine(x);
}
}
Consider the startProcessing
method. Imagine all four threads arrive at the while
check together. Since the loop check isn't synchronized, all four might enter the loop when num
is, say, 2.
while
check. Sees num > 0. Enters loop. Releases context. while
check. Num is still 2, thus > 0. while
check. The Decrement operations will all be nicely atomic and synchronized, but you'll still be at -2 when the four operations complete. You have the > 0 test, but it's performed outside the lock. Thus, your currentIndex
might be incremented more than 1000 times - as much as 1003 times, I think, one extra increment for each extra thread that might have entered the loop unnecessarily. I've modified your original program slightly (moved around Thread.Sleep
s and Console.WriteLine()
s, to make the race condition easier to detect. Also, I changed the top count to 11, not a multiple of 4 (the number of threads that you spawn). Theoretically, this doesn't change the behavior at all, just how likely it is to occur.
class Program
{
private static object syncLock = new object();
int num = 0;
// make static so we can read in Main()
static int currentIndex = 0;
static void Main(string[] args)
{
Program p = new Program();
p.num = 11;
p.callThreadCreation();
while (p.num > 0)
{
Thread.Sleep(1000);
}
Console.WriteLine("All done {0}", currentIndex);
Console.Read();
}
public void callThreadCreation()
{
for (int i = 1; i <= 4; i++)
{
string name = "T" + i;
Thread T = new Thread(new ThreadStart(() => startProcessing()));
T.Name = name;
T.Start();
}
}
private void startProcessing()
{
while (num > 0)
{
int tempIndex;
lock (syncLock)
{
tempIndex = currentIndex;
}
// putting this in front of the increment/decrement operations makes the race condition more likely
print(tempIndex);
Interlocked.Decrement(ref num);
Interlocked.Increment(ref currentIndex);
Thread.Sleep(1000);
}
}
private void print(int x)
{
Console.WriteLine(x);
}
}
If you try this program, eventually you should see the program output 12 at the end. Below is the program I came up with to fix the race condition. I admit it's not the most elegant, but in such a simple example it probably doesn't need to be so elegant.
class Program
{
private readonly object syncLock = new object();
private int num = 0;
private static int currentIndex = 0;
private static void Main(string[] args)
{
Program p = new Program();
p.num = 11;
// just return the threads, so we can join
var threadList = p.callThreadCreation();
// don't need Thread.Sleep() here
//while (p.num > 0)
//{
// Thread.Sleep(1000);
//}
foreach (var t in threadList) { t.Join(); }
Console.WriteLine("All done {0}", currentIndex);
Console.Read();
}
public IReadOnlyList<Thread> callThreadCreation()
{
var threadList = new List<Thread>();
for (int i = 1; i <= 4; i++)
{
// this is easier to read for me
var T = new Thread(startProcessing)
{
Name = "T" + i
};
// just return the threads, so we can join
threadList.Add(T);
T.Start();
}
// just return the threads, so we can join
return threadList;
}
private void startProcessing()
{
while (true)
{
int tempIndex;
lock (syncLock)
{
// I'm not sure what the point of this line is... sometimes "tempIndex" will
// be the same between two threads, but this just seems like a progress report
// for the person watching the program so I didn't fix this.
tempIndex = currentIndex;
num--;
if (num < 0) break;
}
print(tempIndex);
Interlocked.Increment(ref currentIndex);
Thread.Sleep(1000);
}
}
private void print(int x)
{
Console.WriteLine(x);
}
}
This one should be free of a race condition. The idea is that the increment
operation on the value of contention ( num
) is inside of the same lock as what is checking the value num
for completion. In your example, this isn't true, so there could be a context switch at an inopportune time to make num
change more times than you want. This is also the idea behind why I changed where Console.WriteLine()
happens: to increase this chance of an inopportune context switch.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.