如果我只有一个读写器,我需要锁吗;It’’It’时间不重要
本文关键字:It 时间 不重要 有一个 读写器 如果 | 更新日期: 2023-09-27 18:19:58
我有一个程序,它有一个用户界面。
当用户按下按钮时,程序会加载一些数据,这将需要一分钟左右的时间,因此界面自然无法更新。
因此,我正在做以下操作——我有一个名为"hasLoaded"的布尔变量,加载代码(在自己的线程上工作)将在完成后将其设置为true。界面将每隔一段时间进行检查,并停止显示"加载"屏幕。
问题是,由于这是一个共享变量,您可能想要锁定它(或者使用读写器瘦锁之类的)。锁显然只围绕共享变量("hasLoaded")。然而,自:
- 只有一个阅读器
- 只有一个作家
- 如果接口读取了"错误"的值,也没关系,因为它将在几分之一秒内再次检查
我还需要锁吗?需要澄清的是,如果两个线程都试图同时读取它,是否会出现任何"数据损坏"警告或其他问题,或者可能发生的"最糟糕"的事情是它一次读取错误的值,下一次又正确地获取它?
由于它是一种小于或等于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
与新的async
和await
关键字结合使用。如果做得正确,甚至不需要hasLoaded
标志就可以实现这一点,而且它看起来也会更优雅。
您需要将布尔值声明为volatile。
如MSDN上所述:
volatile关键字表示字段可能由同时执行的多个线程。以下字段声明的volatile不受编译器优化的约束假设由单个线程进行访问。这确保了字段中始终存在最新值。
如果您不将其声明为volatile,编译器可能会优化您的代码,使其他线程永远看不到标志的更新。
对于您的示例,您不需要锁定布尔值,但如果您可能想对代码进行注释,以表明您选择不锁定的原因,因为您可能会重新访问此代码,并对共享布尔值的"脏"读取有不同的期望。