2009-05-30 10 views
6

Próbuję napisać pozbawioną blokady wersję kolejki połączeń, której używam do przekazywania wiadomości. To nie jest nic poważnego, tylko po to, aby dowiedzieć się więcej o wątkach.Jak określić odpowiednik zmiennej w VB.net?

Jestem względnie pewny, że mój kod jest poprawny, z wyjątkiem sytuacji, gdy instrukcje są ponownie zamawiane lub wykonywane w rejestrach. Wiem, że mogę korzystać z barier pamięci, aby przestać zmieniać kolejność, ale jak mogę zagwarantować, że wartości są natychmiast zapisywane w pamięci?

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 

Odpowiedz

3

Nie jestem ekspertem w tej sprawie, więc mam nadzieję, że ktoś inny mnie poprawi, jeśli się mylę. Z tego co rozumiem, kwestia optymalizacji pamięci jest obecnie teoretyczna, a niekoniecznie coś, co zajdzie w rzeczywistości. Ale powiedziawszy to, myślę, że używanie Interlockowanego API do dostępu do pamięci (bez względu na MemoryBarrier) nie będzie miało wpływu.

Niestety nie ma odpowiednika dla lotnego w VB.NET. Nie jest ozdobiony zwykłym atrybutem, ale jest raczej specjalnym modyfikatorem generowanym przez kompilator. Musisz użyć Reflection do emisji typu z takim polem.

Oto zasób, do którego często się odwołuję, gdy mam pytania dotyczące wątków w środowisku .NET. Jest bardzo długi, ale mam nadzieję, że okaże się przydatny.

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

+0

Teoretyczny? Masz na myśli, że sekcje krytyczne nie są absolutnymi zabójcami wydajności dla 512 procesorów? – EFraim

10

Nie ma odpowiednik C# 's volatile słów kluczowych w VB.NET. Zamiast tego często zaleca się używanie MemoryBarrier. może również być napisane metody pomocnika:

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 

Również tam jest przydatna blog post na ten temat.

+1

Przydatne, ale nadal jestem zdezorientowany, dlaczego bariera pamięci czyta przychodzi po zamiast, a na odwrót dla zapisów. –

+0

@Strilanc: z dokumentu na temat odpowiedzi poniżej: Każdy odczyt, który pojawia się po lotnym odczycie w sekwencji instrukcji, pojawia się również po lotnym odczycie w modelu pamięci - nie można go zmienić przed odczytem zmiennym.Lotny zapis idzie odwrotnie - każdy zapis, który pojawia się przed lotnym zapisem w sekwencji instrukcji, występuje również przed zmiennym zapisem w modelu pamięciowym. – EFraim

-1

Można również napisać atrybut na "lotny" za pomocą Thread.VolatileRead() i Thread.VolatileWrite() i dokonać wszystkich właściwości/zmiennych z tego atrybutu jak:

<Volatile()> 
Protected Property SecondsRemaining as Integer 

Wrote to gdzieś, ale nie może tego teraz znaleźć ...

2

Zaczynając od .NET 4.5, dodały dwie nowe metody do BCL, aby zasymulować słowo kluczowe volatile: Volatile.Read i Volatile.Write. Powinny one być całkowicie równoważne z odczytywaniem/pisaniem pola volatile. Możesz wyraźnie używać ich w VB.NET. Są lepiej (gdzie lepiej == szybciej) niż Thread.VolatileRead/Thread.VolatileWrite ponieważ używają pół płoty zamiast pełnych ogrodzeń.

Powiązane problemy