我正在尝试编写用于消息传递的调用队列的无锁版本。这并不是为了任何严肃的事情,只是为了了解线程。

我相对确定我的代码是正确的,除非指令被重新排序或在寄存器中完成。我知道我可以使用内存屏障来停止重新排序,但如何确保值立即写入内存?

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
有帮助吗?

解决方案

使用Thread.VolatileRead()VolatileWrite()方法从BCL。

http://msdn.microsoft.com/ EN-US /库/ system.threading.thread.volatileread.aspx

其他提示

有没有在VB.NET C#的volatile关键字等效。相反,什么是通常建议是使用内存屏障的。辅助方法也可以写成:

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

另外还有一个有用的博客帖子在这个问题上。

从 .NET 4.5 开始,他们向 BCL 添加了两个新方法来模拟 volatile 关键词: 易失性.读取易失性.写. 。它们应该完全等同于读/写 volatile 场地。您可以在 VB.NET 中清楚地使用它们。他们是 更好的 (在哪里 更好的 == 快点) 比 Thread.VolatileRead/Thread.VolatileWrite 因为他们使用半栅栏而不是全栅栏。

我不是这方面的专家,所以希望别人会纠正我,如果我错了。据我了解,内存优化的问题是,目前的理论之一,东西不一定会发生在现实中。不过话说回来,我觉得通过联锁API为你的内存访问(不管内存屏障),你将不会受到影响。

不幸的是不适合的易失性VB.NET等效。这不是一个正常的属性来修饰,相反却是一个特殊的编译器生成的改性剂。您将需要使用反射发出这一领域的一种类型。

下面是当我有大约在.NET框架线程问题的资源,我经常参考。这是很长,但是希望你会发现它是有用的。

http://www.yoda.arachsys.com/csharp/threads /printable.shtml

在Mono.Cecil能做到读取器代码使的FieldType作为 RequiredModifierType与ModifierType作为System.Runtime.CompilerServices.IsVolatile。

您也可以编写使用Thread.VolatileRead()和Thread.VolatileWrite(为“挥发性”的属性),并所有属性/用那种属性变量:

<Volatile()>
Protected Property SecondsRemaining as Integer

写到这地方,但似乎无法马上找到它......

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top