如果从未读取值,让多个线程写入同一个 bool 是否安全?
Is it safe to have multiple threads writing to the same bool if the value is never read?
我想出了一个有趣的情况。我有一个 bool 变量,然后我希望多个线程执行自己的独立任务,然后根据线程的结果标记该 bool。此变量 never 由任何线程读取,并且在所有写入测试完成之前 never 使用。像这样:
public bool F(){
bool flag = false;
Parallel.ForEach(list, element =>
{
bool result = // Do independent work here;
if (result) flag = true;
});
return flag;
}
请注意,我从来没有读过 Parallel.ForEach 中的标志。但是可能发生的情况是有多个线程试图将 true 写入标志(但绝不会为 false)。这样做安全吗?
当您 return 时,您正在阅读 flag
。因为没有锁定,所以不能保证调用 F()
的线程将读取最新值。 flag
的更新值有可能(虽然不太可能)位于处理器缓存中,如果没有某种内存屏障,它不会被刷新。
编辑:在对@dasblinkenlight 的回答的评论中,他说“.NET 保证所有副作用,包括写入变量,都在方法returns 时完成。”在那种情况下,这个答案是错误的。基于他的代表和我对线程的持续不确定性,我倾向于相信他,但我在这里问到的 some questions 的答案让我对内存可见性感到非常紧张。所以我不知道。不要过于肯定这个答案。
是的,这样做绝对安全。唯一可能发生的事情是多个线程同时将 true
写入 flag
,因此您不知道哪个线程最终会覆盖什么结果,但最终结果将是相同的。
当您读取标志时,所有的写入都已完成,并且除了 true
之外,您再也不会尝试将任何内容写入 flag
。因此,您只能看到以下两种情况:
- None 的线程将任何内容写入
flag
- 在这种情况下 flag
将保持 false
,或者
- 一个或多个线程将
true
写入 flag
- 在这种情况下,标志将设置为 true
.
如果您想完全避免这种情况,并可能节省一些执行时间,您可以这样做:
return list.AsParallel().Any(element => {
bool result = // Do independent work here
...
return result;
});
此代码不会产生等效的执行路径,因为如果其中一个线程 returns true
,执行可能会提前停止。如果这是不可取的,保持你的 ForEach
方法也很好。
从根本上说,您的策略没有任何问题。同时写入 flag
严格来说不会成为问题,因为您使用它的语义有限。这里真正的问题是方法末尾的 flag
的最终读取是否安全。在这种特殊情况下,它可能是,但只是偶然。 Parallel.For
很可能在您不知情的情况下注入内存屏障,以防止线程缓存 false
值或以其他方式读取陈旧值。但是,我确实建议您养成在读取变量时通过显式调用 Volatile.Read
(或任何其他内存屏障生成机制)来表明您的意图的习惯。
我想出了一个有趣的情况。我有一个 bool 变量,然后我希望多个线程执行自己的独立任务,然后根据线程的结果标记该 bool。此变量 never 由任何线程读取,并且在所有写入测试完成之前 never 使用。像这样:
public bool F(){
bool flag = false;
Parallel.ForEach(list, element =>
{
bool result = // Do independent work here;
if (result) flag = true;
});
return flag;
}
请注意,我从来没有读过 Parallel.ForEach 中的标志。但是可能发生的情况是有多个线程试图将 true 写入标志(但绝不会为 false)。这样做安全吗?
当您 return 时,您正在阅读 flag
。因为没有锁定,所以不能保证调用 F()
的线程将读取最新值。 flag
的更新值有可能(虽然不太可能)位于处理器缓存中,如果没有某种内存屏障,它不会被刷新。
编辑:在对@dasblinkenlight 的回答的评论中,他说“.NET 保证所有副作用,包括写入变量,都在方法returns 时完成。”在那种情况下,这个答案是错误的。基于他的代表和我对线程的持续不确定性,我倾向于相信他,但我在这里问到的 some questions 的答案让我对内存可见性感到非常紧张。所以我不知道。不要过于肯定这个答案。
是的,这样做绝对安全。唯一可能发生的事情是多个线程同时将 true
写入 flag
,因此您不知道哪个线程最终会覆盖什么结果,但最终结果将是相同的。
当您读取标志时,所有的写入都已完成,并且除了 true
之外,您再也不会尝试将任何内容写入 flag
。因此,您只能看到以下两种情况:
- None 的线程将任何内容写入
flag
- 在这种情况下flag
将保持false
,或者 - 一个或多个线程将
true
写入flag
- 在这种情况下,标志将设置为true
.
如果您想完全避免这种情况,并可能节省一些执行时间,您可以这样做:
return list.AsParallel().Any(element => {
bool result = // Do independent work here
...
return result;
});
此代码不会产生等效的执行路径,因为如果其中一个线程 returns true
,执行可能会提前停止。如果这是不可取的,保持你的 ForEach
方法也很好。
从根本上说,您的策略没有任何问题。同时写入 flag
严格来说不会成为问题,因为您使用它的语义有限。这里真正的问题是方法末尾的 flag
的最终读取是否安全。在这种特殊情况下,它可能是,但只是偶然。 Parallel.For
很可能在您不知情的情况下注入内存屏障,以防止线程缓存 false
值或以其他方式读取陈旧值。但是,我确实建议您养成在读取变量时通过显式调用 Volatile.Read
(或任何其他内存屏障生成机制)来表明您的意图的习惯。