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

本文关键字:竞争 条件 多线程 何处理 处理 | 更新日期: 2023-09-27 18:09:59

下面是一个例子:

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

如果在条件和BeginInvoke调用之间的控件被处置,例如?

另一个与事件有关的例子:

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

如果MyEvent在第一行和If语句之间取消订阅,If语句仍将执行。然而,这是合适的设计吗?如果取消订阅还带来了正确调用事件所必需的状态破坏,该怎么办?这个解决方案不仅有更多的代码行(样板),而且不够直观,可能会导致客户端出现意想不到的结果。

你说呢,SO?

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

在我看来,如果这是一个问题,你的线程管理和对象生命周期管理都是鲁莽的,需要重新检查。

在第一个例子中,代码是不对称的:BeginInvoke不会等待action完成,但直接调用会等待;这可能已经是一个bug了

如果您期望另一个线程潜在地处理您正在处理的控件,您别无选择,只能捕获ObjectDisposedException -并且它可能不会被抛出,直到您已经在action内部,并且由于BeginInvoke,可能不在当前线程上。

这是不恰当的假设,一旦你从一个事件取消订阅,你将不再收到通知。它甚至不需要多个线程——只需要多个订阅者。如果第一个订阅者在处理通知时执行了某些操作,导致第二个订阅者取消订阅,则当前"正在发送"的通知仍将发送给第二个订阅者。您可以在事件处理程序例程的顶部使用一个保护子句来缓解这种情况,但您无法阻止它的发生。

有几种解决竞争条件的技术:

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

哪个选项是"正确的"取决于应用程序。每个选项都有性能权衡,这可能会降低并发性的好处。

如果这种行为在您的应用程序中蔓延到多个地方,那么可能需要重新设计API,如下所示:

if(!control.invokeIfRequired()){
    action(action);
}

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