计划中的竞争条件

时间:2015-07-07 16:56:46

标签: c# multithreading

我是线程新手。所以,我只是一个接一个地创建了4个专用线程(而不是使用Tasks),并为它们提供了一些工作。但是,它让我怀疑startProcessing函数中的竞争条件,我只是将currentIndex上的值存储到lock中的局部变量。但是,我跑了很多次,但不能满足没有种族问题或其他任何问题。

请帮我清除这件事。

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);
    }

}

2 个答案:

答案 0 :(得分:1)

考虑startProcessing方法。想象一下,所有四个线程一起到达while支票。由于循环检查未同步,因此当num为2时,所有四个都可能进入循环。

  1. 第一个帖子到达while检查。见号num&gt; 0.进入循环。发布背景。
  2. 第二个帖子到达while检查。 Num仍为2,因此&gt; 0.
  3. 重复第三和第四个线程。
  4. 现在你处于竞争状态。所有四个线程都在循环体内,并且不受while检查的保护。递减操作都将很好地原子化和同步,但是当四个操作完成时,你仍然处于-2。你有&gt; 0测试,但它在锁外执行。因此,您的currentIndex可能会增加1000倍以上 - 我认为,对于可能不必要地进入循环的每个额外线程,可增加1003倍。

答案 1 :(得分:0)

我稍微修改了您的原始程序(在Thread.SleepConsole.WriteLine()附近移动,以便更容易检测到竞争条件。此外,我将最高计数更改为11,而不是4的倍数(你产生的线程数)。从理论上讲,这根本不会改变行为,只是可能的发生方式。

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);
    }
}

如果您尝试此程序,最终您应该在结尾看到程序输出12。以下是我提出的解决竞争条件的计划。我承认它并不是最优雅的,但在这么简单的例子里,它可能并不需要如此优雅。

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);
    }
}

这个应该没有竞争条件。这个想法是对争用值(increment)的num操作在与检查值num完成的内容相同的锁内。在您的示例中,这不是真的,因此可能会在不合适的时间进行上下文切换,以使num更改次数超出您的预期。这也是为什么我在Console.WriteLine()发生变化的原因背后的想法:增加不合时宜的上下文切换的机会。