使用SyncLock的正确方法(一般情况下)

时间:2011-01-26 05:23:11

标签: vb.net multithreading optimization locking

这是关于锁定两个List(Of T)对象的previous question的后续行动。那里的答案很有帮助,但又给我留下了另一个问题。

假设我有这样的功能:

Public Function ListWork() As Integer
  List1.Clear()
  ..Some other work which does not modify List1..
  List1.AddRange(SomeArray)
  ..Some more work that does not involve List1..
  Return List1.Count
End Function

位于声明List1的类中。在多线程环境中,我现在明白我应该为List1设置一个私有锁定对象,并在修改或枚举时锁定List1。我的问题是,我应该这样做:

Private List1Lock As New Object
Public Function ListWork() As Integer
  SyncLock List1Lock
    List1.Clear()
  End SyncLock
  ..Some other work which does not modify List1..
  SyncLock List1Lock
    List1.AddRange(SomeArray)
  End SyncLock
  ..Some more work that does not involve List1..
  SyncLock List1Lock
    Dim list1Count As Integer = List1.Count
  End SyncLock
  Return list1Count
End Function

或者这个:

Private List1Lock As New Object
Public Function ListWork() As Integer
  SyncLock List1Lock
    List1.Clear()
    ..Some other work which does not modify List1..
    List1.AddRange(SomeArray)
    ..Some more work that does not involve List1..
    Dim list1Count As Integer = List1.Count
  End SyncLock
  Return list1Count
End Function

我猜这个前一个例子是最优的?

2 个答案:

答案 0 :(得分:9)

从例子中可以看出哪一个是正确的,如果是的话。一些指导/观察虽然可以帮助您回答您的问题,或者知道提供更多信息的内容:

首先,你必须同步吗?对于每个线程来说,拥有这个类的实例会更有意义吗?如果每个实例都是该线程的本地实例,并且只在该线程上进行了修改和使用,则不需要锁定。

如果此类和利用线程的目的是并行处理更大的数据集,那么主线程可能更合理地以某种逻辑方式划分任务,然后等待工作线程完成。在这种情况下,不要自己管理线程,而是查看ThreadPool并等待句柄。那时大部分肮脏的工作都是为你完成的。

关于同步/锁定一般情况:如果您的操作在步骤之间中断,数据是否一致/有效?

在您的示例中,假设您有两个主题。第一个是在.AddRange().Count之间的区域,当第二个线程出现时进入函数,并获取列表上的锁。

线程1运行了一点,然后点击守护.Count方法的锁,然后进入休眠状态。同时线程2清除列表,然后释放其锁定,唤醒线程1,然后获取锁定。

在这种情况下,当线程1完成工作以构建列表时,线程1将从此函数返回0。然后,列表长度实际上不是0,因为线程2已经出现并填充了列表。

在这种情况下,单个列表操作周围的锁会破坏程序,因此在ClearCount调用之间有一个锁定更有意义。

简而言之,多线程是引入与Race Conditions相关的一整类微妙错误的好方法,通常会导致Heisenbugs

通常情况下,尽可能避免使用线程。如果不能,请尝试以需要最少同步的方式安排工作负载(例如,在开始时为线程提供一组数据,然后等待它发出完成信号,例如链接的线程池示例)。如果你不能这样做,那么请小心谨慎,并始终问自己“如果两个线程在这个区域运行会发生什么”。

希望这有助于您在未来的多线程代码中进行冒险。

答案 1 :(得分:6)

“这取决于”。 这两个例子有不同的语义。

在后一个例子中,整个操作集相对于锁是原子的。虽然在前一个示例中,对列表的访问受到保护,但是整个操作集不能(正确地)被视为原子(相对于锁)。

想象一下,如果在不同线程对同一个对象调用ListWork时进行操作/线程交错,会发生什么/可能发生。

快乐的编码。