2009-05-30 14 views
6

Estoy intentando escribir una versión sin candado de una cola de llamadas que uso para el envío de mensajes. Esto no es para nada serio, solo para aprender a enhebrar.¿Cómo especifico el equivalente de volátil en VB.net?

Estoy relativamente seguro de que mi código es correcto, excepto si las instrucciones se vuelven a solicitar o se realizan en registros. Sé que puedo usar barreras de memoria para detener el reordenamiento, pero ¿cómo puedo asegurar que los valores se escriban en la memoria de inmediato?

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 

Respuesta

3

No soy un experto en este tema, así que espero que alguien más me corrija si me equivoco. Por lo que entiendo, el tema de las optimizaciones de la memoria es actualmente teórico y no necesariamente algo que ocurrirá en la realidad. Pero una vez dicho esto, creo que al usar la API enclavada para su acceso a memoria (independientemente de MemoryBarrier) no se vería afectado.

Desafortunadamente no hay un equivalente para volátil en VB.NET. No está decorado con un atributo normal, sino que es un modificador generado por el compilador especial. Debería usar Reflection para emitir un tipo con este tipo de campo.

Este es un recurso al que me refiero a menudo cuando tengo preguntas sobre cómo enhebrar en .NET Framework. Es muy largo pero espero que lo encuentres útil.

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

+0

¿Teórico? ¿Te refieres a que las secciones críticas no son asesinos de rendimiento absoluto para más de 512 máquinas CPU? – EFraim

10

No hay equivalente de volatile palabra clave C# 's en VB.NET. En cambio, lo que a menudo se recomienda es el uso de MemoryBarrier. métodos de ayuda también se podrían escribir:

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 

También hay un blog útil post sobre este tema.

+1

Útil, pero todavía estoy confundido por qué la barrera de memoria de lectura viene después en lugar de antes, y viceversa para las escrituras. –

+0

@Strilanc: del documento en la respuesta a continuación: cada lectura que ocurre después de una lectura volátil en la secuencia de instrucciones ocurre después de la lectura volátil también en el modelo de memoria; no pueden reordenarse antes de la lectura volátil.Una escritura volátil va al revés: cada escritura que ocurre antes de una escritura volátil en la secuencia de instrucciones ocurre antes de la escritura volátil también en el modelo de memoria. – EFraim

-1

También puede escribir un atributo de "volátil" usando Thread.VolatileRead() y Thread.VolatileWrite() y hacer todas las propiedades/variables con ese atributo como:

<Volatile()> 
Protected Property SecondsRemaining as Integer 

Has escrito no parecen esto en alguna parte, pero para encontrarlo en este momento ...

2

a partir de .NET 4.5, se añaden dos nuevos métodos para el BCL para simular el volatile palabra clave: Volatile.Read y Volatile.Write. Deben ser totalmente equivalentes a la lectura/escritura de un campo volatile. Puedes usarlos claramente en VB.NET. Son mejor (donde mejor == más rápido) que el Thread.VolatileRead/Thread.VolatileWrite porque utilizan vallas medio en lugar de vallas completos.

Cuestiones relacionadas