7

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

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

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
4

6 回答 6

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

还有一篇关于这个主题的有用的博客文章。

于 2009-05-30T08:27:37.280 回答
6

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

http://msdn.microsoft.com/en-us/library/system.threading.thread.volatileread.aspx

于 2010-11-09T00:36:48.457 回答
4

从 .NET 4.5 开始,他们向 BCL 添加了两个新方法来模拟volatile关键字:Volatile.ReadVolatile.Write。它们应该完全等同于读/写一个volatile字段。您可以清楚地在 VB.NET 中使用它们。它们比/更好更好==更快),因为它们使用半栅栏而不是全栅栏。Thread.VolatileReadThread.VolatileWrite

于 2015-08-16T06:15:16.510 回答
3

我不是这方面的专家,所以如果我错了,希望其他人能纠正我。据我了解,内存优化问题目前是理论上的问题,不一定会在现实中发生。但是话虽如此,我认为通过使用 Interlocked API 进行内存访问(无论 MemoryBarrier 是什么),您都不会受到影响。

不幸的是,在 VB.NET 中没有 volatile 的等价物。它不是用普通属性修饰的,而是一个特殊的编译器生成的修饰符。您需要使用反射来发出具有这种字段的类型。

这是我在对 .NET 框架中的线程有疑问时经常参考的资源。它很长,但希望你会发现它很有用。

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

于 2009-05-30T06:57:00.707 回答
0

Mono.Cecil 阅读器代码将 FieldType 设为 RequiredModifierType,ModifierType 设为 System.Runtime.CompilerServices.IsVolatile。

于 2018-06-20T11:42:11.877 回答
-1

您还可以使用 Thread.VolatileRead() 和 Thread.VolatileWrite() 为“Volatile”编写一个属性,并使用该属性创建所有属性/变量,例如:

<Volatile()>
Protected Property SecondsRemaining as Integer

在某个地方写了这个,但现在似乎找不到它......

于 2013-12-20T16:52:13.097 回答