2009-05-30 6 views
6

Я пытаюсь написать бесплатную версию очереди вызовов, которую я использую для передачи сообщений. Это не для чего-то серьезного, просто для того, чтобы узнать про потоки.Как указать эквивалент volatile в VB.net?

Я уверен, что мой код верен, за исключением случаев, когда инструкции переупорядочены или сделаны в регистрах. Я знаю, что могу использовать барьеры памяти, чтобы остановить переупорядочение, но как я могу обеспечить, чтобы значения сразу записывались в память?

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 

ответ

3

Я не специалист по этой теме, поэтому, надеюсь, кто-то другой исправит меня, если я ошибаюсь. Насколько я понимаю, вопрос оптимизации памяти в настоящее время является теоретическим, а не обязательно тем, что произойдет в действительности. Но, сказав это, я думаю, что с помощью Interlocked API для доступа к вашей памяти (независимо от MemoryBarrier) вы не пострадали.

К сожалению, в VB.NET нет эквивалента для volatile. Он не украшен нормальным атрибутом, а скорее является специальным модификатором, созданным компилятором. Вам нужно будет использовать Reflection, чтобы испускать тип с таким видом поля.

Вот ресурс, о котором я часто упоминаю, когда у меня есть вопросы о потоковой передаче в .NET framework. Это очень долго, но, надеюсь, вы найдете это полезным.

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

+0

Теоретический? Например, вы имеете в виду, что критические разделы не являются абсолютными убийцами производительности для 512+ процессоров? – EFraim

10

Там нет эквивалента С # volatile ключевое слово в VB.NET. Вместо этого часто рекомендуется использовать MemoryBarrier. Методы Helper также может быть написано:

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

Полезно, но я до сих пор смущен, почему возникает предел памяти чтения, а не раньше, и наоборот для записи. –

+0

@Strilanc: из документа по приведенному ниже ответу: каждое чтение, которое происходит после того, как волатильное считывание в последовательности команд происходит после изменчивого чтения в модели памяти, - они не могут быть переупорядочены до волатильного чтения.Волатильная запись идет в обратном направлении - каждая запись, которая возникает до того, как волатильная запись в последовательности команд возникает перед изменчивой записью в модели памяти. – EFraim

-1

Вы также можете написать атрибут для "Летучего" с помощью Thread.VolatileRead() и Thread.VolatileWrite() и сделать все свойства/переменным с этим атрибутом, как:

<Volatile()> 
Protected Property SecondsRemaining as Integer 

писал это где-то, но, похоже, не находит его прямо сейчас ...

2

Начиная с .NET 4.5, они добавили два новых метода в BCL для имитации ключевого слова volatile: Volatile.Read и Volatile.Write. Они должны быть полностью эквивалентны чтению/записи поля volatile. Вы можете использовать их в VB.NET. Они лучше (где лучше == быстрее), чем Thread.VolatileRead/Thread.VolatileWrite, потому что они используют половину заборов вместо полных заборов.