如果我只有一个读写器,我需要锁吗;It’’It’时间不重要

本文关键字:It 时间 不重要 有一个 读写器 如果 | 更新日期: 2023-09-27 18:19:58

我有一个程序,它有一个用户界面。

当用户按下按钮时,程序会加载一些数据,这将需要一分钟左右的时间,因此界面自然无法更新。

因此,我正在做以下操作——我有一个名为"hasLoaded"的布尔变量,加载代码(在自己的线程上工作)将在完成后将其设置为true。界面将每隔一段时间进行检查,并停止显示"加载"屏幕。

问题是,由于这是一个共享变量,您可能想要锁定它(或者使用读写器瘦锁之类的)。锁显然只围绕共享变量("hasLoaded")。然而,自:

  1. 只有一个阅读器
  2. 只有一个作家
  3. 如果接口读取了"错误"的值,也没关系,因为它将在几分之一秒内再次检查

我还需要锁吗?需要澄清的是,如果两个线程都试图同时读取它,是否会出现任何"数据损坏"警告或其他问题,或者可能发生的"最糟糕"的事情是它一次读取错误的值,下一次又正确地获取它?

如果我只有一个读写器,我需要锁吗;It’’It’时间不重要

由于它是一种小于或等于CPU体系结构的字大小的数据类型,因此您无需担心是否会出现数据损坏问题。写入和读取将是原子的。

但这不是问题所在。问题是,在某些情况下,该标志的读取器可能永远不会看到它更改为true值。这实际上很容易用下面的代码来演示。您需要使用Release配置进行编译,然后在不附加调试器的情况下运行它。您可能会注意到,该程序从未结束有效地演示错误。

// * Must be compiled as RELEASE and ran outside of a debugger.
class Program
{   
    // Decorate with volatile to change the behavior.
    static bool stop = false;
    public static void Main(string[] args)
    {
        var t = new Thread(() =>
        {
            Console.WriteLine("thread begin");
            bool toggle = false;
            while (!stop)
            {
                toggle = !toggle;
            }
            Console.WriteLine("thread end");
        });
        t.Start();
        Thread.Sleep(1000);
        stop = true;
        Console.WriteLine("stop = true");
        Console.WriteLine("waiting...");
        // The Join call should return almost immediately.
        // With volatile it DOES.
        // Without volatile it does NOT.
        t.Join();         
    }
}

在你的具体情况下,这可能实际上是没有意义的。原因是作者是后台线程,而读者是UI线程。这一点在这里至关重要,因为内存屏障将保证在后台线程结束时提交写入,这可能是在hasLoad设置为true之后。在UI线程方面,消息泵本身可能会在您不知情的情况下注入内存屏障。因此,每次检查hasLoaded的值(可能是用某种计时器)时,您都可能会得到最新的值。

无论如何,如果它在没有锁或使用volatile的情况下工作,那只是偶然的。帮你自己一个忙,采取适当的保护措施,只需拿起锁。

更好的是,将Task与新的asyncawait关键字结合使用。如果做得正确,甚至不需要hasLoaded标志就可以实现这一点,而且它看起来也会更优雅。

您需要将布尔值声明为volatile。

如MSDN上所述:

volatile关键字表示字段可能由同时执行的多个线程。以下字段声明的volatile不受编译器优化的约束假设由单个线程进行访问。这确保了字段中始终存在最新值。

如果您不将其声明为volatile,编译器可能会优化您的代码,使其他线程永远看不到标志的更新。

对于您的示例,您不需要锁定布尔值,但如果您可能想对代码进行注释,以表明您选择不锁定的原因,因为您可能会重新访问此代码,并对共享布尔值的"脏"读取有不同的期望。