multithreading - 在 F# 中设置标志的线程问题
问题描述
TLDR:我在回调中设置了一个标志,它的值在主循环中没有改变。
不幸的是,由于原始代码很大,我必须制作代码的简化版本,但简而言之,这是我面临的问题:
try
try
exchangeBus.OnTradeEvent.AddHandler(tradeEventHandler)
exchangeBus.OnOrderEvent.AddHandler(orderEventHandler)
let mutable keepGoing = true
while keepGoing do
let nodeRunner = buildRunner()
let waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset)
// handle the completion event
nodeRunner.OnCompletionEvent.Add(fun (completionType: CycleCompletionType) ->
match completionType with
| CodeException ex -> reportError side ex true
keepGoing <- false
abortHandle.Set() |> ignore
| Abort reason -> // do some stuff
keepGoing <- false
abortHandle.Set() |> ignore
| AutoAbort reason -> // do some stuff
waitHandle.Set() |> ignore
| NormalClose -> // do some stuff
waitHandle.Set() |> ignore
)
// start the runner
nodeRunner.Startup()
// wait for completion, or abort
WaitHandle.WaitAny([| waitHandle; abortHandle |]) |> ignore
with ex ->
status <- ExceptionState
getRunner(side) ||> (fun r -> r.Signal(CycleCompletionType.CodeException(ex)))
getRunner(side.Opposite) ||> (fun r -> r.Signal(CycleCompletionType.Abort($"exception on the {side.Opposite} side, aborting")))
finally
exchangeBus.OnTradeEvent.RemoveHandler(tradeEventHandler)
exchangeBus.OnOrderEvent.RemoveHandler(orderEventHandler)
所以,简而言之,它是如何工作的:
您有两个包含此代码的对象并行运行。他们实现了一个循环,在其中创建了一个对象,该对象包含一个图并基于该图中的节点执行一系列代码。
在执行图表的代码之外,回调tradeEventHandler和orderEventHandler中可能会发生异常,但这是单独处理的。
一旦执行图的核心(nodeRunner)完成,它就会发送一个事件来描述它是如何完成的。有 2 种情况可以(正常和 AutoAbort)和 2 种情况需要停止进程(CodeException 和 Abort)。
有两个 EventWaitHandle 对象,一个是该对象唯一的,用于控制循环(waitHandle),另一个是两个对象共有的(abortHandle),用于告诉它们停止操作。
当其中一个对象进入中止状态时,就会发生此问题。
OnCompletionEvent回调被执行,然后我们发生了两件事:
keepGoing <- false
abortHandle.Set() |> ignore
正如预期的那样,在WaitHandle.WaitAny调用之后设置了 abortHandle 等待并继续执行。
keepGoing标志(该循环的本地)被设置为 false 并且......循环继续进行,就好像它仍然是真的一样。当我在循环开始时打印它的状态时,它仍然显示为真而不是假。
所以一定有一个线程相关的问题。我不知道OnCompletionEvent调用在哪里将keepGoing标志设置为 false,但它肯定与循环中使用的值不同。
我该如何解决这个问题?或者,我错过了什么明显的东西吗?
解决方案
您可能需要使用Volatile
或Interlocked
使修改keepGoing
在线程间立即可见。
由于keepGoing
不是您无法应用的字段,因此您可以通过引用[<VolatileField>]
将其传递给Volatile
方法,如下所示:
let mutable keepGoing = true
while (Volatile.Read(&keepGoing)) do
// Do your processing
// And eventually disable keepGoing
Volatile.Write(&keepGoing, false)
或者将其定义为参考单元格并像这样使用它:
let keepGoing = ref true
while (Volatile.Read(keepGoing)) do
// Do your processing
// And eventually disable keepGoing
Volatile.Write(keepGoing, false)
这可能是必要的,因为正如以下注释中Volatile
所解释的:
在多处理器系统上,易失性写入操作可确保写入内存位置的值立即对所有处理器可见。易失性读取操作获取任何处理器写入内存位置的最新值。这些操作可能需要刷新处理器缓存,这会影响性能。
在单处理器系统上,易失性读取和写入可确保将值读取或写入内存而不是缓存(例如,在处理器寄存器中)。因此,您可以使用这些操作来同步对可由另一个线程或硬件更新的字段的访问。
... 一些语言,如 Visual Basic,不承认易失性内存操作的概念。Volatile 类以此类语言提供该功能。
因此,当您的一个线程修改 的值时keepGoing
,其他线程可能没有及时看到更改,因为它们已经将该值提取到处理器缓存或寄存器中。
也可以看看:
原子性、易变性和不变性是不同的,第三部分Eric Lippert。
推荐阅读
- ios - 如何将存储属性添加到 Swift 结构扩展
- android - 在 Android 11 上从“/apex/com.android.runtime/lib64/bionic/libc.so”中止崩溃
- nullpointerexception - 杰克逊解析json中缺少字段时出错java.lang.NullPointerException
- python - python pandas groupby eventType和eventId并获取每个eventType之间的日期差异
- azure - 保护访问以触发 Azure 逻辑应用(HTTP 请求触发器)
- c# - 字符串到字节数组到文件以用于 POST 请求
- google-cloud-sql - 如何从 Cloud Build 连接到 Cloud SQL 以运行 knex 数据库迁移?
- graphql - GraphQL 多值 eq 过滤器
- java - Keycloak 和 SSL 连接在运行模式下工作,在测试模式下失败
- database-design - Tack - 预定的视频通话网络应用程序设计