如何处理多线程中的竞态条件?

5

这是一个例子:

if (control.InvokeRequired)
{
    control.BeginInvoke(action, control);
}
else
{
    action(control);
}

如果在条件和BeginInvoke调用之间,控件被释放了,会怎样呢?
另一个例子涉及事件:
var handler = MyEvent;

if (handler != null)
{
    handler.BeginInvoke(null, EventArgs.Empty, null, null);
}

如果在第一行和if语句之间取消订阅"MyEvent",if语句仍将被执行。然而,这是合适的设计吗?如果取消订阅也意味着必要状态的破坏,以便正确调用事件怎么办?这个解决方案不仅有更多的代码行(样板文件),而且不太直观,可能会导致客户端出现意外结果。
SO,你怎么看?

上述事件处理程序模式存在的原因是为了保持对处理程序的引用,以便它不会被释放。 - Mitch Wheat
@Mitch Wheat:是的,我并不是说处理程序必定被处理,而是当客户端取消订阅事件后,他们可能还会决定不再需要某种通常只由事件处理程序使用的状态对象。因为事件在取消订阅后仍然以不幸的时机运行,所以可能会出现非常难以跟踪的错误,因为预期结果是在取消订阅后不会运行处理程序。 - Ryan Peschel
@Mitch Wheat:也许我没有正确理解这个问题。假设应用程序是多线程的,而客户端在第一行和if语句之间取消订阅事件,那么事件是否仍然会运行? - Ryan Peschel
1
你可能想阅读Eric Lippert的文章,了解为什么事件存在这种可能性以及为什么调用者有责任确保他们不会清除状态或适当处理状态故障。 - Chris Hannon
3个回答

5
在我看来,如果这些问题中的任何一个是存在的,那么你的线程管理和对象生命周期管理都很鲁莽,需要重新审视。
在第一个示例中,代码不对称:BeginInvoke不会等待action完成,但直接调用会;这可能已经是一个 bug 了。
如果你希望另一个线程可能会处理掉你正在使用的控件,则除了捕获ObjectDisposedException之外别无选择——而且它可能不会被抛出,直到你已经在action内部了,并且可能不在当前线程上,这要归功于BeginInvoke
假设你取消了事件的订阅后就不会再收到通知,这是不合适的。即使没有多个线程也可能发生这种情况——只有多个订阅者。如果第一个订阅者在处理通知时做了什么导致第二个订阅者取消订阅,当前正在“传输”中的通知仍将发送给第二个订阅者。你可能可以通过在事件处理程序例程的顶部设置一个保护性条件来减轻这种情况,但无法阻止它发生。

我会仔细查看代码,以允许一个订阅者导致取消订阅同一事件的第二个订阅者。 - Mitch Wheat

3

解决竞态条件有几种技术:

  • 使用互斥锁将整个过程包裹起来。确保每个线程在开始竞争之前必须先获取锁。这样,一旦获取锁,您就知道没有其他线程正在使用该资源,因此可以安全地完成。
  • 找到一种检测和恢复它们的方法;这可能非常棘手,但某些应用程序类型效果很好;处理这种情况的典型方法是保持资源更改次数的计数器;如果完成任务后发现版本号与开始时不同,请读取新版本并从头开始执行任务(或仅失败)。
  • 重新设计应用程序以仅使用原子操作;基本上,这意味着使用可以在一步中完成的操作;这通常涉及“比较和交换”操作,或将所有事务数据适合单个磁盘块中,可以原子地写入。
  • 重新设计应用程序以使用无锁技术;仅当满足硬实时约束比服务每个请求更重要时,才有意义选择此选项,因为无锁设计固有地丢失数据(通常是某些低优先级性质的数据)。

哪种选项是“正确”的取决于应用程序。每个选项都有性能权衡,这可能会使并发的好处变得不那么吸引人。


-1
如果这种行为在您的应用程序中多个地方都存在,那么它可能值得重新设计API,其外观类似于:
if(!control.invokeIfRequired()){
    action(action);
}

这个想法与标准JDK库中的ConcurrentHashMap.putIfAbsent(...)相同。当然,你需要在这个新的control.invokeIfRequired()方法内部处理同步。


第二个例子非常有趣,因为其他线程可能会将“handler”分配为空。我们需要一个锁对象,在这里,每次对“handler”的读写都由相同的锁保护。我不确定C#。但在Java中,工作代码可能如下所示:synchronized(lockObj){if(handler!=null){handler.beginInvoke(...)}} - James Gan
1
-1 这个建议虽然有用,但不是问题的答案。 - Jake T.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接