Csharp/C#教程:受欢迎的“挥发性民意调查旗帜”模式是否破裂?分享


受欢迎的“挥发性民意调查旗帜”模式是否破裂?

假设我想在线程之间使用布尔状态标志来进行协作取消。 (我意识到最好使用CancellationTokenSource ;这不是这个问题的重点。)

 private volatile bool _stopping; public void Start() { var thread = new Thread(() => { while (!_stopping) { // Do computation lasting around 10 seconds. } }); thread.Start(); } public void Stop() { _stopping = true; } 

:如果我在0s调用Start()而在另一个线程调用3s的Stop() ,那么循环是否保证在当前迭代结束时在10s左右退出?

我见过的绝大多数消息来源表明上述内容应该按预期工作; 见: MSDN ; Jon Skeet ; 布赖恩吉迪恩 ; 马克格拉维尔 ; Remus Rusanu 。

但是, volatile只会在读取时生成一个获取栅栏,并在写入时生成一个释放栅栏:

易失性读取具有“获取语义”; 也就是说,保证在指令序列之后的任何内存引用之前发生。 易失性写入具有“释放语义”; 也就是说,保证在指令序列中的写指令之前的任何存储器引用之后发生。 ( C#规格 )

因此,正如Joseph Albahari所观察到的那样,无法保证不会(似乎)交换易失性写入和易失性读取。 因此,后台线程可能会在当前迭代结束后继续读取_stopping (即false )的陈旧值。 具体来说,如果我在0s调用Start()而在3s调用Stop() ,则后台任务可能不会按预期在10s终止,而是在20s,或30s,或者根本不会。

基于获取和释放语义 ,这里有两个问题。 首先,易失性读取将被约束为从存储器中刷新字段(抽象地说)不是在当前迭代结束时,而是在后续迭代结束时,因为获取栅栏发生读取本身之后。 其次,更重要的是,没有什么可以强制volatile写入将值提交到内存,因此无法保证循环将永远终止。

请考虑以下顺序流程:

 Time | Thread 1 | Thread 2 | | 0 | Start() called: | read value of _stopping | |  | ↑ | set _stopping to true | ↑ 4 | ↓ | ↑ 5 | ↓ | ↑ 6 | ↓ | ↑ 7 | ↓ | ↑ 8 | ↓ | ↑ 9 | ↓ | ↑ 10 | ↓ | read value of _stopping | ↓ | <----- acquire-fence ------------ 11 | ↓ | 12 | ↓ | 13 | ↓ | ↑ 14 | ↓ | ↑ 15 | ↓ | ↑ 16 | ↓ | ↑ 17 | ↓ | ↑ 18 | ↓ | ↑ 19 | ↓ | ↑ 20 | | read value of _stopping | | <----- acquire-fence ------------ 

最重要的部分是内存栅栏,标有--><-- ,它们代表线程同步点。 _stopping的易失性读取最多只能(似乎)被移动到其线程的前一个获取栅栏。 但是,易失性写入可以(似乎)无限期地向下移动,因为在其线程上没有其他释放栅栏。 换句话说,写入_stopping任何读取之间没有“ 同步 ”(“发生之前”,“是 – 可见 – ”)关系。

PS我知道MSDN对volatile关键字提供了非常强有力的保证。 但是,专家的共识是MSDN不正确(并没有得到ECMA规范的支持):

MSDN文档声明使用volatile关键字“确保始终在字段中存在最新值”。 这是不正确的,因为正如我们在前面的例子中看到的那样,可以重新排序读后跟读。 ( Joseph Albahari )

如果我在0s调用Start()而在另一个线程调用3s的Stop() ,那么循环是否保证在当前迭代结束时在10s左右退出?

是的,7秒绝对足以让一个线程完成_stopping变量的更改。

几乎是正式的解释

对于提供任何类型的可见性障碍(内存顺序)的每个变量,任何语言的规范都应该提供以下保证:

finit有界时间段内,将在其他线程中观察到来自一个线程的变量(具有特殊内存顺序)的任何更改

没有这个保证,即使变量的内存顺序function也没用。

C#的规范肯定提供了关于volatile变量的保证,但我找不到相应的文本。

注意,关于有限时间的这种保证与记忆订单保证无关(“获取”,“释放”等),并且不能障碍和记忆顺序的定义中推断出

正式 – 非正式的解释

什么时候说

我在3点叫Stop()

一个暗示,有一些可见的效果 (例如,打印到终端的信息),这允许他声称大约3s时间戳(因为在Stop() 之后发出了print语句)。

随着C#规范优雅地播放(“10.10执行顺序”):

执行应继续进行,以便在关键执行点保留每个执行线程的副作用。 副作用定义为易失性字段的读取或写入,对非易失性变量的写入,对外部资源的写入以及抛出exception。 应保留这些副作用的顺序的关键执行点是对volatile字段(第17.4.3节),锁定语句(第15.12节)以及线程创建和终止的引用。

假设打印是一个关键的执行点 (可能它使用了锁),您可能会确信,此时为其他线程分配的_stopping volatile变量的副作用可见的 ,该线程会检查给定的变量。

非正式的解释

虽然允许编译器在代码中向前移动volatile变量的赋值,但它无法无限期地执行此操作:

CPU的一侧,情况更简单:没有CPU会比有限数量的指令更多地分配给存储器单元。

总的来说 ,只能在非常有限数量的指令上去除对volatile变量的赋值。

上述就是C#学习教程:受欢迎的“挥发性民意调查旗帜”模式是否破裂?分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/cdevelopment/1030273.html

(0)
上一篇 2022年1月13日
下一篇 2022年1月13日

精彩推荐