当前位置: 首页 > 编程语言 > C#

流行的“volatilepollflag”模式坏了吗?分享

时间:2023-04-10 15:45:29 C#

流行的“易变投票旗”模式坏了吗?假设我想在线程之间使用布尔状态标志来进行协作取消。(我意识到最好使用CancellationTokenSource;这不是这个问题的重点。)privatevolatilebool_stopping;publicvoidStart(){varthread=newThread(()=>{while(!_stopping){//计算持续约10秒。}});线程。开始();}publicvoidStop(){_stopping=true;Q:如果我在另一个线程中在0s时调用Start(),在3s时调用Stop(),那么循环是否保证在当前迭代结束时10s左右退出?我看到的绝大多数资料表明上述内容应该按预期工作;参见:MSDN;乔恩双向飞碟;布赖恩·吉迪恩;马克砾石;莱姆斯·鲁萨努。但是,volatile仅在读取时生成获取栅栏,在写入时生成释放栅栏:volatile读取具有“获取语义”;也就是说,保证在指令序列之后的任何内存引用之前发生。易失性写入具有“释放语义”;也就是说,保证在指令序列中写指令之前的任何内存引用之后发生。(C#规范)因此,正如JosephAlbahari观察到的那样,无法保证易失性写入和易失性读取不会(似乎)被交换。因此,后台线程可能会在当前迭代结束后继续读取_stopping的陈旧值(即false)。具体来说,如果我在0秒调用Start()并在3秒调用Stop(),则后台任务可能不会按预期在10秒终止,而是在20秒或30秒终止,或者根本不终止。基于acquire和release语义,这里有两个问题。首先,volatile读取将被限制从内存中刷新字段(抽象地说),而不是在当前迭代结束时,而是在后续迭代结束时,因为获取栅栏发生在读取本身之后。其次,更重要的是,没有任何东西可以强制易失性写入将值提交到内存中,因此无法保证循环会终止。考虑以下顺序流:时间|线程1|线程2||0|开始()调用:|读取_stopping的值|||↑|将_stopping设置为true|↑4|↓|↑5|↓|↑6|↓|↑7|↓|↑8|↓|↑9|↓|↑10|↓|读取_stopping的值|↓|<-----获取围栏----------11|↓|12|↓|13|↓|↑14|↓|↑15|↓|↑16|↓|↑17|↓|↑18|↓|↑19|↓|↑20||读取_stopping的值||<-----acquire-fence------------最重要的部分是内存栅栏,标记为-->和<--,代表线程同步点。_stoppingvolatileread最多只能(似乎)移动到其线程的先前获取栅栏。但是,易失性写入可以(表面上)无限期地向下移动,因为在其线程上没有其他释放栅栏。换句话说,写_停止任何读取之间没有“同步”(“发生在之前”,“是可见的”)关系。PS我知道MSDN对volatile关键字提供了非常有力的保证。然而,专家一致认为MSDN是不正确的(并且不受ECMA规范支持):MSDN文档指出使用volatile关键字“确保最新值始终存在于该字段中”。这是不正确的,因为正如我们在前面的示例中看到的,可以在读取后重新排序读取。(JosephAlbahari)如果我在另一个线程的0秒调用Start()并在3秒调用Stop(),循环是否保证在当前迭代结束时大约10秒退出?是的,7秒绝对足以让一个线程完成更改_stopping变量。几乎正式的解释是,对于提供任何类型的可见性障碍(内存顺序)的每个变量,任何语言的规范都应该提供以下保证:在finit和有限的时间段内,将在其他线程中观察到来自一个的任何更改到线程的变量(具有特殊的内存顺序)。没有这种保证,即使是变量的内存排序功能也是无用的。C#的规范当然对volatile变量有保证,但是我找不到对应的文字。请注意,这种关于有限时间的保证与内存顺序保证(“获取”、“释放”等)无关,并且不能从障碍和内存顺序的定义中推断出来。正式-非正式解释何时说我在3处调用了Stop()一个提示,具有一些可见的效果(例如,一条消息打印到终端),这允许他声称大约3s时间戳(因为在Stop()发出打印之后陈述)。随着C#规范的顺利执行(“10.10执行顺序”):执行应继续进行,以便在关键执行点保留每个执行线程的副作用。副作用定义为读取或写入易失性字段、写入非易失性变量、写入外部资源和抛出异常。应保留这些副作用顺序的关键执行点是对易失性字段(§17.4.3)、锁定语句(§15.12)以及线程创建和终止的引用。假设打印是执行的关键点(也许它使用锁),您可以确信_stopping为其他线程分配的volatile变量的副作用在此时是可见的,并且该线程检查给定的变量。非正式解释虽然允许编译器在代码中向前移动volatile变量的赋值,但它不能无限期地这样做:从CPU方面来看,情况更简单:没有CPU会执行超过分配给内存单元的有限数量的指令。通常,只能在非常有限的指令中删除对volatile变量的赋值。C#学习教程就是这样:流行的“volatilepollflag”模式被打破了吗?如果所有分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: