当联锁类可用时,为什么在.NET中使用同步锁进行简单操作?

当联锁类可用时,为什么在.NET中使用同步锁进行简单操作?,.net,multithreading,locking,interlocked,synclock,.net,Multithreading,Locking,Interlocked,Synclock,我在VB.NET中做简单的多线程已经有一段时间了,刚刚开始我的第一个大型多线程项目。我总是使用Synclock语句来完成所有事情,因为我认为没有更好的方法 我刚刚了解了Interlocked类-它使它看起来像是所有这些: Private SomeInt as Integer Private SomeInt_LockObject as New Object Public Sub IntrementSomeInt Synclock SomeInt_LockObject So

我在VB.NET中做简单的多线程已经有一段时间了,刚刚开始我的第一个大型多线程项目。我总是使用
Synclock
语句来完成所有事情,因为我认为没有更好的方法

我刚刚了解了
Interlocked
类-它使它看起来像是所有这些:

Private SomeInt as Integer
Private SomeInt_LockObject as New Object

Public Sub IntrementSomeInt
    Synclock SomeInt_LockObject
        SomeInt += 1
    End Synclock
End Sub
可以用单个语句替换:

Interlocked.Increment(SomeInt)
这将在内部处理所有锁定并修改编号。这比为简单操作编写自己的锁要简单得多(运行时间更长或更复杂的操作显然仍然需要自己的锁)


当我可以使用
联锁
方法完成同样的事情时,我为什么要使用专用的锁定对象来滚动我自己的锁定呢?

你是对的<代码>联锁应在此处使用,且速度将快于
同步

但是,
联锁
类并不为人所知


但是,在某些情况下,您需要使用
SyncLock
,而
Interlocked
则没有帮助。

这是因为没有人知道它。传播消息

简单的答案是,使用
监视器
锁(VB中的
SyncLock
和C#中的
lock{}
)不仅可以确保一次只有一个线程可以访问变量(或者严格意义上说,一次只有一个线程可以获得锁定对象的锁),但它也创建了所需的内存屏障,以确保变量上的读取不会被优化

如果您从未简单地读取变量的值(换句话说,所有工作都是通过调用
Interlocked
)完成的,那么您就没事了。但是,如果您需要能够正常读取变量,那么情况就更复杂了。无锁读/写通常在C#中使用
volatile
关键字完成。这将指示编译器在使用变量的任何地方读取变量的值,而不是将这些读取优化到本地缓存中。不幸的是,在VB.NET中没有等价物,所以您必须使用其他东西


被接受的答案应该提供更多关于您可以做什么的信息。简言之,大多数人在VB.NET中使用
SyncLock
,因为它比不使用
SyncLock
时所需的逻辑更简单,也不复杂
Interlocked
仅限于对整数、长型和布尔型等的简单操作


例如,如果您想将一个项目添加到共享列表(共T个)中,您仍然需要
SynClock

我曾经读过一篇关于所谓的非原子和原子(在VB:interlocked中)操作的非常好的解释,并将尝试对其进行总结

Normal "non-atomic" operations consist of several steps 
->其他线程可以在这些线程之间工作

"Atomic" operations consist of one only one step 
->在处理原子操作时,其他线程无法执行工作,原子操作始终作为一个整体进行处理

联锁类是此类原子操作的集合,因此定义为线程安全的。 即使多个线程对同一变量执行读写操作,这些操作也是绝对线程安全的

但是,这些线程安全命令的组合可能是不安全的,因为在原子操作之间可能会出现竞争条件

因此,如果您想比较两个变量,然后增加较小的变量,这是不线程安全的,即使它们本身的单个操作是(interlocked.compare,interlocked.increment)。 在这里,您仍然必须使用同步锁

除此限制外,联锁系统没有“隐藏的不良一面”

a=5的消旋条件的一个示例:

 Thread1: a+=1
 Thread2: a+=2    
 --> supposed to be 8, but can be only 6 or 7,
 but can also be 6 or 7 depending on which thread wins the race
备选案文1:

T1 step 1: read 5
T1 step 2: add 1 = 6
T1 step 3: write 6
T2 step 1: read 6
T2 step 2: add 2 = 8
T2 step 3: write 8
--> is 8 as supposed
或选择2:

T1 step 1: read 5
T2 step 1: read 5
T1 step 2: add 1 = 6
T2 step 2: add 2 = 7
T2 step 3: write 7
T1 step 3: write 6
--> is only 6
T2 step 1: read 5, add 2, write 7
T1 step 1: read 7, add 1, write 8
或备选方案3:

T1 step 1: read 5
T2 step 1: read 5
T1 step 2: add 1 = 6
T2 step 2: add 2 = 7
T1 step 3: write 6
T2 step 3: write 7
--> is only 7
带联锁。增量:

备选案文1:

T1 step 1: read 5, add 1, write 6
T2 step 1: read 6, add 2, write 8
或选择2:

T1 step 1: read 5
T2 step 1: read 5
T1 step 2: add 1 = 6
T2 step 2: add 2 = 7
T2 step 3: write 7
T1 step 3: write 6
--> is only 6
T2 step 1: read 5, add 2, write 7
T1 step 1: read 7, add 1, write 8
->在所有情况下,a=8为假定的螺纹安全解决方案

通过将这个简单的示例应用于有问题的代码,可以解决这里发布的所有问题

希望这能帮助谷歌搜索这个话题的其他人。
Janis

你能举一个这样的例子吗?如果我只是做一个简单的递增/递减操作(或者如果我需要将值更改为1以上,则使用.Add),那么在所有情况下,联锁都会更好。如果我需要更高级的东西(比如一个4步流程,我需要保证它是在任何给定时间运行它的唯一线程),我需要一些同步。我感兴趣的是一种情况,联锁似乎是合适的,但不是一个好的选择。如果您使用两个不同的变量,
联锁
是不够的。这就是我所想的-我看到它从1.0开始就存在了,所以当我刚刚发现它时,我感到震惊。我想“这里肯定有其他我看不到的东西,否则这种锁定方法会更受欢迎”。我真的只是想确保它的第一眼看上去一样棒。这个答案没有传达足够的信息,也没有直接回答问题。不完全正确,当你用Interlocked更改变量时,你可以安全地读取它们,而无需任何特殊措施。因为写操作周围有屏障,所以读操作是安全的。@Henk:Interlocked调用之后的多个读操作不能优化为单个缓存读操作吗?Adam,是的,它们可以。从实时角度来看,这可能看起来是“错误的”,但不会导致不正确的行为。检查联锁构件。只有64位值的读取(在32位系统上)@Henk:如果另一个线程在缓存后更新了该值,那么它的行为怎么可能不是不正确的呢?在这种情况下,变量在
增量
调用后可能只读取一次。除非另一个线程在调用
Increme之间处理其操作