Multithreading 带设置标志的线程问题,以F为单位#

Multithreading 带设置标志的线程问题,以F为单位#,multithreading,f#,Multithreading,F#,TLDR:我在回调中设置了一个标志,它的值在主循环中不会改变 不幸的是,我不得不对代码进行简化,因为原始版本相当大,但简而言之,我面临的问题是: try try exchangeBus.OnTradeEvent.AddHandler(tradeEventHandler) exchangeBus.OnOrderEvent.AddHandler(orderEventHandler) let mutable keepGoing = true

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)        
试试看
尝试
exchangeBus.OnTradeEvent.AddHandler(tradeEventHandler)
exchangeBus.OnOrderRevent.AddHandler(orderEventHandler)
让可变的keepGoing=true
在继续做的时候
让nodeRunner=buildRunner()
让waitHandle=新的EventWaitHandle(false,EventResetMode.ManualReset)
//处理完成事件
nodeRunner.OnCompletionEvent.Add(乐趣(completionType:CycleCompletionType)->
将completionType与
|CodeException ex->reportError side ex true
继续忽略
|中止原因->//做一些事情
继续忽略
|自动中止原因->//做一些事情
waitHandle.Set()|>忽略
|NormalClose->//做一些事情
waitHandle.Set()|>忽略
)
//开始跑步
nodeRunner.Startup()
//等待完成,或中止
WaitHandle.WaitAny([| WaitHandle;abortHandle])|>忽略
使用ex->
状态(fun r->r.Signal(CycleCompletionType.CodeException(ex)))
getRunner(side.contract)| |>(fun r->r.Signal(CycleCompletionType.Abort({side.contract}侧的$”异常,中止”))
最后
exchangeBus.OnTradeEvent.RemoveHandler(tradeEventHandler)
exchangeBus.OnOrderRevent.RemoveHandler(orderEventHandler)
简而言之,它是如何工作的:

您有两个包含此代码的对象并行运行。它们实现了一个循环,在该循环中,它们创建了一个保存图形的对象,并基于该图形中的节点执行一系列代码

在执行图形的代码之外,在回调tradeEventHandler和orderEventHandler中可能会发生异常,但这是单独处理的

一旦执行图形(nodeRunner)的核心完成,它将发送一个事件,描述它是如何完成的。有2种情况是ok(正常和自动中止),2种情况是进程需要停止(CodeException和中止)

有两个EventWaitHandle对象,一个是此对象的唯一对象,用于控制循环(waitHandle),另一个是这两个对象的公用对象(abortHandle),用于通知它们停止操作

当其中一个对象进入中止状态时,就会发生此问题

执行OnCompletionEvent回调后,会发生两件事:

keepGoing <- false
abortHandle.Set() |> ignore
keepGoing忽略
设置abortHandle等待,并在WaitHandle.WaitAny调用后按预期继续执行

keepGoing标志(该循环的本地标志)设置为false,并且。。。循环继续,好像它仍然是真的。 当我在循环开始时打印它的状态时,它仍然显示true而不是false

因此,一定存在与线程相关的问题。我不知道OnCompletionEvent调用在何处将keepGoing标志设置为false,但它肯定与循环中使用的值不同

我怎样才能解决这个问题?或者,我是否遗漏了一些明显的内容?

您可能需要使用或修改
keepGoing
,以便在线程之间立即可见

由于
keepGoing
不是一个无法应用的字段,因此可以按如下方式将其传递给
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)
这可能是必要的,因为如中所述:

在多处理器系统上,易失性写入操作可确保写入内存位置的值立即对所有处理器可见。易失性读取操作获取任何处理器写入内存位置的最新值。这些操作可能需要刷新处理器缓存,这可能会影响性能

在单处理器系统上,易失性读写可确保值被读取或写入内存而不是缓存(例如,在处理器寄存器中)。因此,您可以使用这些操作来同步对可以由另一个线程或硬件更新的字段的访问

。。。某些语言(如Visual Basic)不认识易失性内存操作的概念。Volatile类以这种语言提供了这种功能

因此,可能是当您的一个线程修改了
keepGoing
的值时,其他线程没有及时看到更改,因为它们已经将该值提取到处理器缓存或寄存器中

另见:

  • 埃里克·利珀特


您可能需要使用
Volatile
Interlocked
来修改
keepGoing
以跨线程传播。尝试将
keepGoing
定义为引用,即
让keepGoing=ref true
然后执行
Volatile.Write(keepGoing,false)
Volatile.Read(keepGoing)
。这很有效,但问题是为什么?因为这是一个bool,写应该是原子的,我在设置等待之前写object@Thomas-这是必要的,因为处理器可能缓存了
keepGoing
的值,因此来自一个处理器的写入可能不会反映在第二个处理器的缓存中。看一看,还有。我更新了我的答案以包含这个解释。我认为问题可能更多地出现在JIT编译器中,而不是与CPU相关:因为我设置了值,然后设置了wait对象,然后检查了变量,所以在执行过程中没有并发性。很可能编译器没有以某种方式更新值i