简体   繁体   中英

Race condition in program

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.

  1. First thread arrives at while check. Sees num > 0. Enters loop. Releases context.
  2. Second thread arrives at while check. Num is still 2, thus > 0.
  3. Repeat for third and fourth threads.
  4. Now you're in the race condition. All four threads are inside the loop body and unprotected by the 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM