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

时间:2011-09-27 00:05:04

标签: c# multithreading race-condition

以下是一个例子:

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语句。但是,这是正确的设计吗?如果使用取消订阅也会破坏正确调用事件所需的状态?这个解决方案不仅具有更多代码行(样板文件),而且它不那么直观,并且可能导致客户端出现意外结果。

你说什么,是吗?

3 个答案:

答案 0 :(得分:5)

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

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

如果你期望另一个线程可能处理你正在使用的控件,你别无选择,只能抓住ObjectDisposedException - 并且在你已经在action之前可能不会抛出它。 1}},并且由于BeginInvoke而可能不在当前线程上。

假设一旦您取消订阅活动,您将不再收到有关通知的通知,这是不恰当的。它甚至不需要多个线程来实现 - 只有多个订户。如果第一订户在处理导致第二订户取消订阅的通知时做某事,则当前“在飞行中”的通知仍将转到第二订户。您可以使用事件处理程序例程顶部的guard子句来缓解此问题,但您无法阻止它发生。

答案 1 :(得分:3)

有一些解决竞争条件的技巧:

  • 用互斥体包裹整个东西。确保在每个线程甚至可以在比赛中开始运行之前必须首先获得锁定。这样,只要你获得锁定,就知道没有其他线程正在使用该资源,你可以安全地完成。
  • 找到一种方法来检测并从中恢复;这可能非常棘手,但某些应用程序运行良好;解决这个问题的一种典型方法是保持资源变化次数的计数器;如果您完成任务并发现版本号与您启动时的版本号不同,请阅读新版本并从头开始执行任务(或者只是失败)
  • 重新设计应用程序以仅使用原子操作;基本上这意味着使用可以一步完成的操作;这通常涉及“比较和交换”操作,或者将所有事务的数据拟合到可以原子方式编写的单个磁盘块中。
  • 重新设计应用程序以使用无锁技术;只有满足硬实时约束比服务每个请求更重要时,此选项才有意义,因为无锁设计本身会丢失数据(通常具有一些低优先级性质)。

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

答案 2 :(得分:-1)

如果此行为在应用程序中传播多个位置,则可能需要重新设计API,如下所示:

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

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