问题
I'm new to threading and I came accross a custom thread pool implementation example in a blog. I'm pasting just the necessary parts of the code:
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
My question is, why do we need a lock to implement the readonly ThreadCount property?
回答1:
The lock will force a memory barrier, so that a stale value from the CPU cache isn't read if the last value written was written by a different CPU. The same could be done with Thread.VolatileRead()
without locking.
回答2:
This code should be using Interlocked.CompareExchange to access the value in the property getter. Set param3 (comparand) to something that you know cannot be seen in the variable, like Int32.MinValue
, and then the function just returns the current value of _count
.
If Interlocked
operations are used for all accesses to the variable, the lock is redundant since all access via Interlocked
class methods is atomic.
回答3:
I have no idea why the author choose to use a lock in one part of the class while utilizing lock-free techniques in other parts. However, I can make an assumption that the author did it to create an explicit memory barrier on the read of the Interger
. VB does not contain the equivalent of C#'s volatile
keyword so that leaves just 4 other common methods for making the read safe. I have listed these in the order that I would choose for this specific scenario.
- Interlocked.CompareExchange
- Thread.VolatileRead
- Thread.MemoryBarrier
- SyncLock
The memory barrier is required to prevent the VB or JIT compilers from moving instructions around. The most likely optimization in the absence of a memory barrier is to lift the read outside of a loop. Consider this realistic use of the ThreadCount
property.
Sub LoggingThread()
Do While True
Trace.WriteLine(ThreadPool.ThreadCount)
Loop
End Sub
In this example the CLR would likely inline ThreadCount
and then potentially "lift" the read of _Count
and cache it in a CPU register before the loop begins. The effect would be that the same value would always be displayed.1
1In reality the Trace.WriteLine
call itself generates a memory barrier that would cause the code to be safe by accident. The example was intended as a simple illustration of what could happen.
回答4:
It makes no sense, as there is no lock where you modify the property. Maybe the code was not using Interlocked operations before, and was using SyncLock even in Open / Close? In such case SyncLock would be really needed in the read access as well.
来源:https://stackoverflow.com/questions/3853678/why-lock-is-needed-to-implement-a-readonly-int-property