【问题标题】:Why there is thread race at the beginning?为什么一开始就有线程竞赛?
【发布时间】:2013-03-04 12:32:16
【问题描述】:

我最近尝试写一个lock语句的例子。考虑以下代码:

public partial class Form1 : Form
    {
        private class Concurrency
        {
            private int _myValue;
            private object _locker = new object();
            public int Value
            {
                set
                {
                    lock (_locker)
                    {
                        _myValue = value;
                        Thread.Sleep(new Random().Next(5, 25));
                    }
                }

                get
                {
                    return _myValue;
                }
            }
        }

        private Random _random;
        private Concurrency _concurrency;

        public Form1()
        {
            InitializeComponent();
            _random = new Random();
            _concurrency = new Concurrency();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            CreateTask(1);
            CreateTask(2);
        }

        private void CreateTask(int taskId)
        {
            Task.Factory.StartNew(() =>
                {
                    for (int i = 0; i < 10; ++i)
                    {
                        int randomNumber = _random.Next(0, 50);

                        Console.WriteLine("Thread {0}, setting value {1}", taskId, randomNumber);
                        _concurrency.Value = randomNumber;
                        Console.WriteLine("Thread {0}, getting value {1}", taskId, _concurrency.Value);

                        Thread.Sleep(_random.Next(5, 15));
                    }
                });
        }
    }

结果是:

Thread 2, setting value 4
Thread 1, setting value 22
Thread 2, getting value 22
Thread 1, getting value 22
Thread 2, setting value 11
Thread 2, getting value 11
Thread 1, setting value 8
Thread 2, setting value 41
Thread 1, getting value 8
Thread 1, setting value 30
Thread 2, getting value 41
Thread 1, getting value 30
Thread 2, setting value 18
Thread 1, setting value 42
Thread 2, getting value 18
Thread 2, setting value 30
Thread 1, getting value 42
Thread 1, setting value 24
Thread 2, getting value 30
Thread 1, getting value 24
Thread 2, setting value 13
Thread 1, setting value 7
Thread 2, getting value 13
Thread 2, setting value 13
Thread 1, getting value 7
Thread 2, getting value 13
Thread 1, setting value 38
Thread 2, setting value 19
Thread 1, getting value 38
Thread 1, setting value 4
Thread 2, getting value 19
Thread 2, setting value 44
Thread 1, getting value 4
Thread 2, getting value 44
Thread 1, setting value 48
Thread 2, setting value 12
Thread 1, getting value 48
Thread 1, setting value 47
Thread 2, getting value 12
Thread 1, getting value 47

如您所见,除了第一次设置/获取情况外,一切都很好:线程 2 设置值 2 但得到 22。这不是单一情况,它每次都会发生。我知道设置和获取不是原子的,应该围绕任务中的指令设置锁定,但为什么第一次尝试总是失败而其他工作正常?

编辑:

我将并发类更新为:

private class Concurrency
        {
            private static Random _random = new Random();
            private int _myValue;
            private object _locker = new object();
            public int Value
            {
                set
                {
                    lock (_locker)
                    {
                        _myValue = value;
                        Thread.Sleep(_random.Next(5, 250));
                    }
                }

                get
                {
                    return _myValue;
                }
            }
        }

请注意,我还在 Thread.Sleep 中扩展了时间范围。结果是:

Thread 2, setting value 3
Thread 1, setting value 9
Thread 2, getting value 9
Thread 2, setting value 44
Thread 1, getting value 9
Thread 1, setting value 35
Thread 2, getting value 44
Thread 2, setting value 32
Thread 1, getting value 35
Thread 1, setting value 25
Thread 2, getting value 32
Thread 2, setting value 15
Thread 1, getting value 25
Thread 1, setting value 5
Thread 2, getting value 15
Thread 2, setting value 34
Thread 1, getting value 5
Thread 1, setting value 42
Thread 2, getting value 34
Thread 2, setting value 36
Thread 1, getting value 42
Thread 1, setting value 8
Thread 2, getting value 36
Thread 2, setting value 42
Thread 1, getting value 8
Thread 1, setting value 16
Thread 2, getting value 42
Thread 2, setting value 0
Thread 1, getting value 16
Thread 1, setting value 43
Thread 2, getting value 0
Thread 2, setting value 20
Thread 1, getting value 43
Thread 1, setting value 30
Thread 2, getting value 20
Thread 2, setting value 38
Thread 1, getting value 30
Thread 1, setting value 0
Thread 2, getting value 38
Thread 1, getting value 0

真的没有什么改变。我猜这不是 Random 的问题,而是其他的问题。

【问题讨论】:

  • 尝试拉出for循环(从任务内部到button1_Click例程)...

标签: c# locking task


【解决方案1】:

它发生很多次,而不仅仅是第一次

您只“看到”一次,而实际上是您程序中的错误。可能每次您看到两个“设置...”时,您可能会阅读最后一个。想象一下这种情况:

主线程 1 线程 2 值 = 0 整数 x1 = 值 值 = 2 整数 x2 = 值 写线(x1) 写线(x2)

输出正确(线程 1 为 0,线程 2 为 2)。现在想象一下,如果调度是这样的:

主线程 1 线程 2 值 = 0 值 = 2 整数 x1 = 值 写线(x1) 整数 x2 = 值 写线(x2)

您会得到 错误 结果,因为对于两个线程,您都会读取值 2。实际上它不是 错误,因为唯一锁定的操作是集合,那里不保证线程 1 的读取操作(获取属性值)将在线程 2 的写入操作(设置属性值)之前执行。

最后也看看this post,如果你写这个,你会看到这样的代码可能会失败(完全出于同样的原因):

++_concurrency.Value;

【讨论】:

  • 好的,我已经知道了,而且我知道锁的位置是错误的。但是,为什么 - 无论我运行这个应用程序多少次 - 只有第一次设置/获取失败,而其他都可以?
  • Random 对象的“共享”种子可能会有所贡献(毕竟锁定释放和属性获取之间的时间非常短)但关键是您不能依赖/研究/检查不同线程的指令执行顺序。至少没有那么高的水平。
  • 可能(但出于我之前所说的原因,这只是一个无用的猜测),因为第一次运行第二个线程时已经过去了更多时间(因为当您创建第二个任务时,第一个任务是尚未运行)。
【解决方案2】:

正如您所指出的,锁定不正确。所以它更多的是一个“为什么它似乎工作,除了一开始?”的问题。 (我只是重申你的问题。)

[编辑]

由于您更改了代码以消除我所说的问题,所以这里有另一个想法 - 我认为这确实是答案。

按照您的代码方式,线程退出锁和读回值之间的时间非常短。

检查你的二传手:

set
{
    lock (_locker)
    {
        _myValue = value;
        Thread.Sleep(_random.Next(5, 25));
    }
}

现在如果thread1在锁里面,它会设置_myValue然后休眠。线程 2 在此期间将等待进入锁。

当 thread1 退出休眠时,它立即退出锁并继续执行下一行代码,在本例中是打印当前值的行:

Console.WriteLine("Thread {0}, getting value {1}", taskId, _concurrency.Value);

除非线程 1 在退出锁和取消引用 _concurrency.Value 之间被取消调度,否则它将接收正确的值。因为时间很短,所以不太可能在这段时间内被取消。

如果线程1 取消调度,那么线程2 将能够在线程1 取消引用之前进入锁并更改_myValue

做任何事情来增加线程设置和获取值之间的时间将更有可能观察到“不正确”的值。

尝试以下程序,然后取消注释// Try with this sleep uncommented. 指示的行。您会看到更多行打印“数字不匹配”。

using System;
using System.Threading;
using System.Threading.Tasks;


namespace Demo
{
    class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Starting");
            CreateTask(1);
            CreateTask(2);
            Console.ReadKey();
        }

        private static void CreateTask(int taskId)
        {
            Task.Factory.StartNew(() =>
            {
                for (int i = 0; i < 10; ++i)
                {
                    int randomNumber = _random.Next(0, 50);

                    Console.WriteLine("Thread {0}, setting value {1}", taskId, randomNumber);
                    _concurrency.Value = randomNumber;
                    // Thread.Sleep(10); // Try with this sleep uncommented.
                    int test = _concurrency.Value;
                    Console.WriteLine("Thread {0}, getting value {1}", taskId, test);

                    if (test != randomNumber)
                    {
                        Console.WriteLine("Number mismatch.");
                    }

                    Thread.Sleep(_random.Next(5, 15));
                }
            });
        }

        private static Random _random = new Random();
        private static Concurrency _concurrency = new Concurrency();

    }

    class Concurrency
    {
        private int _myValue;
        private object _locker = new object();
        public int Value
        {
            set
            {
                lock (_locker)
                {
                    _myValue = value;
                    Thread.Sleep(_random.Next(5, 25));
                }
            }

            get
            {
                return _myValue;
            }
        }

        static Random _random = new Random();
    }
}

那么为什么一开始就失败了呢?好吧,我认为这只是系统启动线程方式的产物。

【讨论】:

  • 啊,我错过了...我在 Form1 类中看到了共享的 _random,但错过了他们创建 new 随机数的地方。
  • 您很好地解释了 lock 语句不正确的原因,我完全知道这一点,但无论如何感谢。另外 Sleep 也显示了问题。我唯一不明白的是你提到的这个神器。我想这解决了我的问题。
猜你喜欢
  • 1970-01-01
  • 2023-03-24
  • 2011-04-20
  • 1970-01-01
  • 2010-09-14
  • 2014-02-02
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
相关资源
最近更新 更多