2010-10-04 54 views
2

我是线程新手,并且在blog中遇到了自定义线程池实现示例。我刚贴的代码的必要部分:为什么需要锁定来实现只读int属性?

Public Class ThreadPool 

    Private CountLock As New Object 
    Private _Count As Integer 

    Public ReadOnly Property ThreadCount() As Integer 
     Get 
     SyncLock CountLock 
      Return _Count 
     End SyncLock 
     End Get 
    End Property 

    Public Sub Open() 
     Interlocked.Increment(_Count) 
    End Sub 

    Public Sub Close() 
     Interlocked.Decrement(_Count) 
     .... 
    End Sub 

EndClass 

我的问题是,为什么我们需要一个锁来实现只读属性THREADCOUNT?

+0

链接中的代码甚至不太接近类似于线程池的代码。 – jgauffin 2010-10-04 11:52:50

+0

我觉得最后我会把这个页面的链接发给博客的作者:) – aslisabanci 2010-10-04 11:58:07

回答

1

该锁定将强制内存屏障,以便在写入的最后一个值由另一个CPU写入时,不会读取CPU高速缓存中过时的值。 Thread.VolatileRead()也可以不锁定。

1

它没有任何意义,因为在修改属性时没有锁定。也许代码之前没有使用Interlocked操作,并且即使在打开/关闭时也使用SyncLock?在这种情况下,SyncLock在读访问中也是非常需要的。

+0

你确定它没有意义吗?添加锁会增加一个内存屏障,这将阻止指令重新排序,可能会有所作为。 – 2010-10-04 08:40:57

+0

阻止像ThreadCount这样的属性的指令排序?我不这么认为 - 这纯粹是一种信息/调试属性。任何真正的逻辑都将使用_Count完成。 – wj32 2010-10-04 08:44:56

+0

@Lasse对int的单独读取在.NET中是原子的。重新排序只会是一个问题,如果调用者也调用其他成员并且有足够的内联进行:它可能会但意味着紧耦合,这可能意味着锁定应该在调用者中。 – Richard 2010-10-04 08:46:07

2

该代码应该使用Interlocked.CompareExchange来访问属性getter中的值。将参数3(比较数)设置为您知道不能在变量中看到的内容,如Int32.MinValue,然后该函数仅返回当前值_count

如果Interlocked操作用于所有对变量的访问,锁定是多余的,因为通过Interlocked类方法的所有访问都是原子的。

+0

我想我在学习锁的时候错过了一些东西。你能否解释为什么如果他不使用互锁,锁定是必要的,但只需锁定打开/关闭? – aslisabanci 2010-10-04 12:10:13

+0

@davsan - 互锁类使用特定于平台的处理器指令来保证访问原始类型变量(如“Int32”和“Int64”)或本机指针时的原子行为。如果你可以确保'_count'的所有访问都是通过'Interlocked',这将是处理'_count'线程安全的最有效方法。任何更高级别的锁定将会更加昂贵。否则,摆脱'Interlocked'的使用并在任何地方使用'lock(_count)' - 混合和匹配没有意义。 – 2010-10-04 13:27:40

+0

@Steve - 好的,我明白你的解释。我想我在这里想的是:afaik,lock(CountLock){return _count;}限制了一次只有一个线程可以执行{return _count}节,所以如果多个线程在同一时间?我想我不知道阅读安全问题? – aslisabanci 2010-10-04 13:46:06

2

我不知道为什么作者选择在类的一部分中使用锁,同时在其他部分使用无锁技术。但是,我可以假设作者是这样做的,以便在读取Interger时创建一个明确的内存屏障。 VB不包含相当于C#的volatile关键字,因此只剩下4个其他常用方法来使读取安全。我已经按照我为此特定场景选择的顺序列出了这些内容。

  • Interlocked.CompareExchange
  • Thread.VolatileRead
  • Thread.MemoryBarrier
  • 的SyncLock

是必需的存储器阻挡层以防止所述VB或JIT编译从周围移动的指令。在没有内存屏障的情况下,最有可能的优化是将读取提升到循环之外。考虑ThreadCount属性的实际使用。

Sub LoggingThread() 
    Do While True 
    Trace.WriteLine(ThreadPool.ThreadCount) 
    Loop 
End Sub 

在这个例子中,CLR将可能直列ThreadCount然后潜在的“升降机”的_Count读取和循环开始前它缓存在一个CPU寄存器。效果是始终显示相同的值。

在现实中,Trace.WriteLine调用本身产生一个内存屏障,将导致代码被意外安全。这个例子的目的是为了说明可能发生的事情。

相关问题