Pergunta

Eu estou tentando escrever uma versão sem bloqueio de uma chamada de fila que eu uso para a passagem de mensagens. Isto não é para nada de grave, apenas para aprender sobre threading.

Eu sou relativamente certeza que meu código está correto, exceto se as instruções são re-ordenada ou feito nos registos. Eu sei que eu posso usar barreiras de Memória para interromper re-encomenda, mas como posso garantir valores são gravados em memória imediatamente?

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
Foi útil?

Solução

Use Thread.VolatileRead() e VolatileWrite() métodos da BCL.

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

Outras dicas

Não há equivalente a C # 's volatile palavra-chave em VB.NET. Em vez disso o que é muitas vezes recomendado é o uso de MemoryBarrier . métodos auxiliares também poderia ser escrito:

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

Além disso, há um blog útil pós sobre este assunto.

A partir de .NET 4.5, que acrescentou dois novos métodos para o BCL para simular a palavra-chave volatile: Volatile.Read e Volatile.Write . Eles devem ser totalmente equivalente a leitura / escrita de um campo volatile. Você pode claramente usá-los em VB.NET. Eles são melhor (onde melhor == mais rápido ) do que o Thread.VolatileRead / Thread.VolatileWrite porque eles usam meia cercas em vez de cercas completos.

Eu não sou um especialista sobre este assunto por isso espero que alguém vai me corrija se eu estiver errado. Pelo que eu entendo, a questão de otimizações de memória é, actualmente, uma teórica e não necessariamente algo que vai ocorrer na realidade. Mas tendo dito isso, eu acho que usando a API Interlocked para o seu acesso à memória (independentemente do MemoryBarrier) não seriam afetados.

Infelizmente não há um equivalente para volátil no VB.NET. Não é decorada com um atributo normal, mas é sim um compilador especial gerado modificador. Você precisaria usar o reflexo para emitir um tipo com este tipo de campo.

Aqui é um recurso que muitas vezes se referem a quando eu tiver dúvidas sobre enfiar no framework .NET. É muito longa, mas esperamos que você vai encontrá-lo útil.

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

O código leitor Mono.Cecil torna o FieldType Como RequiredModifierType com o ModifierType como System.Runtime.CompilerServices.IsVolatile.

Você também pode escrever um atributo para "Volátil" usando Thread.VolatileRead () e Thread.VolatileWrite () e fazer todas as propriedades / variáveis ??com esse atributo como:

<Volatile()>
Protected Property SecondsRemaining as Integer

escreveu isso em algum lugar, mas não consigo encontrá-lo agora ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top