我正在尝试编写一个用于消息传递的无锁版本的调用队列。这不是严肃的事情,只是为了了解线程。
我相对确定我的代码是正确的,除非指令被重新排序或在寄存器中完成。我知道我可以使用内存屏障来停止重新排序,但是如何确保将值立即写入内存?
Public Class CallQueue
Private first As New Node(Nothing) 'owned by consumer'
Private last As Node = first 'owned by producers'
Private Class Node
Public ReadOnly action As Action
Public [next] As Node
Public Sub New(ByVal action As Action)
Me.action = action
End Sub
End Class
Private _running As Integer
Private Function TryAcquireConsumer() As Boolean
Threading.Thread.MemoryBarrier()
'Dont bother acquiring if there are no items to consume'
'This unsafe check is alright because enqueuers call this method, so we never end up with a non-empty idle queue'
If first.next Is Nothing Then Return False
Threading.Thread.MemoryBarrier()
'Try to acquire'
Return Threading.Interlocked.Exchange(_running, 1) = 0
End Function
Private Function TryReleaseConsumer() As Boolean
Do
Threading.Thread.MemoryBarrier()
'Dont release while there are still things to consume'
If first.next IsNot Nothing Then Return False
Threading.Thread.MemoryBarrier()
'Release'
_running = 0
Threading.Thread.MemoryBarrier()
'It is possible that a new item was queued between the first.next check and releasing'
'Therefore it is necessary to check if we can re-acquire in order to guarantee we dont leave a non-empty queue idle'
If Not TryAcquireConsumer() Then Return True
Loop
End Function
Public Sub QueueAction(ByVal action As Action)
'Enqueue'
'Essentially, this works because each node is returned by InterLocked.Exchange *exactly once*'
'Each node has its .next property set exactly once, and also each node is targeted by .next exactly once, so they end up forming a valid tail'
Dim n = New Node(action)
Threading.Interlocked.Exchange(last, n).next = n
'Start the consumer thread if it is not already running'
If TryAcquireConsumer() Then
Call New Threading.Thread(Sub() Consume()).Start()
End If
End Sub
Private Sub Consume()
'Run until queue is empty'
Do Until TryReleaseConsumer()
first = first.next
Call first.action()
Loop
End Sub
End Class
答案 0 :(得分:11)
VB.NET中没有C#的volatile
关键字。相反,通常建议使用MemoryBarrier。辅助方法也可以写成:
Function VolatileRead(Of T)(ByRef Address As T) As T
VolatileRead = Address
Threading.Thread.MemoryBarrier()
End Function
Sub VolatileWrite(Of T)(ByRef Address As T, ByVal Value As T)
Threading.Thread.MemoryBarrier()
Address = Value
End Sub
此主题还有一个有用的博客post。
答案 1 :(得分:6)
使用BCL中的Thread.VolatileRead()
和VolatileWrite()
方法。
http://msdn.microsoft.com/en-us/library/system.threading.thread.volatileread.aspx
答案 2 :(得分:3)
我不是这方面的专家,所以如果我错了,希望别人能纠正我。根据我的理解,内存优化问题目前是理论问题,并不一定会在现实中发生。但话说回来,我认为通过使用Interlocked API进行内存访问(无论MemoryBarrier),您都不会受到影响。
不幸的是,VB.NET中没有等效的volatile。它没有用普通属性修饰,而是一个特殊的编译器生成修饰符。您需要使用Reflection来发出具有此类字段的类型。
当我对.NET框架中的线程有疑问时,我经常会参考这个资源。这很长,但希望你会发现它很有用。
答案 3 :(得分:0)
Mono.Cecil阅读器代码使FieldType为 将ModifierType设置为System.Runtime.CompilerServices.IsVolatile的RequiredModifierType。
答案 4 :(得分:-1)
您还可以使用Thread.VolatileRead()和Thread.VolatileWrite()为“Volatile”编写属性,并使用该属性创建所有属性/变量,如:
<Volatile()>
Protected Property SecondsRemaining as Integer
在某个地方写了这个,但现在似乎无法找到它......