Vamos tentar o Bloqueio em Go (context.Context)

Olá!



Neste artigo, gostaria de dizer como você poderia fazer seu próprio RWMutex, mas com a capacidade de definir o tempo limite ou acionar o contexto para ignorar o bloqueio. Ou seja, implemente TryLock (context.Context) e RExperimenteLock (context.Context), mas para seu próprio Mutex.



imagem



A imagem mostra como despejar água em um gargalo muito estreito.



Para começar, deve ser esclarecido que para 99% das tarefas, tais métodos não são necessários. Eles são necessários quando o recurso bloqueado pode não ser liberado por um longo tempo. Gostaria de ressaltar que se um recurso bloqueado permanecer ocupado por muito tempo, vale a pena tentar otimizar a lógica no início de forma a minimizar o tempo de bloqueio.



Para obter mais informações, consulte Dançando com Mutexes em Go no Exemplo 2.



Mas se, no entanto, temos que ter uma longa retenção de um fluxo de recursos, então me parece que será difícil prescindir do TryLock.



, , atomic, . , . , , . , , .



Mutex:



// RWTMutex - Read Write and Try Mutex
type RWTMutex struct {
    state int32
    mx    sync.Mutex
    ch    chan struct{}
}


state — mutex, atomic.AddInt32, atomic.LoadInt32 atomic.CompareAndSwapInt32



ch — , .



mx — , , .



:



// TryLock - try locks mutex with context
func (m *RWTMutex) TryLock(ctx context.Context) bool {
    if atomic.CompareAndSwapInt32(&m.state, 0, -1) {
        return true
    }

    // Slow way
    return m.lockST(ctx)
}
// RTryLock - try read locks mutex with context
func (m *RWTMutex) RTryLock(ctx context.Context) bool {
    k := atomic.LoadInt32(&m.state)
    if k >= 0 && atomic.CompareAndSwapInt32(&m.state, k, k+1) {
        return true
    }

    // Slow way
    return m.rlockST(ctx)
}


Como você pode ver, se o Mutex não estiver bloqueado, ele pode ser simplesmente bloqueado, mas se não, passaremos para um esquema mais complexo.



No começo, pegamos o canal, e entramos em um loop infinito, se estiver travado, saímos com sucesso, e se não, então começamos a esperar por um dos 2 eventos, ou que o canal está desbloqueado, ou que o stream ctx.Done () irá desbloquear:



func (m *RWTMutex) chGet() chan struct{} {
    m.mx.Lock()
    if m.ch == nil {
        m.ch = make(chan struct{}, 1)
    }
    r := m.ch
    m.mx.Unlock()
    return r
}

func (m *RWTMutex) lockST(ctx context.Context) bool {
    ch := m.chGet()
    for {
        if atomic.CompareAndSwapInt32(&m.state, 0, -1) {
            return true
        }
        if ctx == nil {
            return false
        }
        select {
        case <-ch:
            ch = m.chGet()
        case <-ctx.Done():
            return false
        }
    }
}

func (m *RWTMutex) rlockST(ctx context.Context) bool {
    ch := m.chGet()
    var k int32
    for {
        k = atomic.LoadInt32(&m.state)
        if k >= 0 && atomic.CompareAndSwapInt32(&m.state, k, k+1) {
            return true
        }
        if ctx == nil {
            return false
        }
        select {
        case <-ch:
            ch = m.chGet()
        case <-ctx.Done():
            return false
        }
    }
}


Vamos desbloquear o mutex.



Precisamos mudar o estado e, se necessário, desbloquear o canal.



Como escrevi acima, se o canal for fechado, o caso <-ch irá ignorar o fluxo de execução.



func (m *RWTMutex) chClose() {
    if m.ch == nil {
        return
    }

    var o chan struct{}
    m.mx.Lock()
    if m.ch != nil {
        o = m.ch
        m.ch = nil
    }
    m.mx.Unlock()
    if o != nil {
        close(o)
    }
}
// Unlock - unlocks mutex
func (m *RWTMutex) Unlock() {
    if atomic.CompareAndSwapInt32(&m.state, -1, 0) {
        m.chClose()
        return
    }

    panic("RWTMutex: Unlock fail")
}
// RUnlock - unlocks mutex
func (m *RWTMutex) RUnlock() {
    i := atomic.AddInt32(&m.state, -1)
    if i > 0 {
        return
    } else if i == 0 {
        m.chClose()
        return
    }

    panic("RWTMutex: RUnlock fail")
}


O mutex em si está pronto, você precisa escrever alguns testes e métodos padrão como Lock () e RLock () para ele



Os benchmarks do meu carro mostraram essas velocidades



  
BenchmarkRWTMutexTryLockUnlock-8        92154297                12.8 ns/op             0 B/op          0 allocs/op
BenchmarkRWTMutexTryRLockRUnlock-8      64337136                18.4 ns/op             0 B/op          0 allocs/op

 RWMutex
BenchmarkRWMutexLockUnlock-8            44187962                25.8 ns/op             0 B/op          0 allocs/op
BenchmarkRWMutexRLockRUnlock-8          94655520                12.6 ns/op             0 B/op          0 allocs/op

 Mutex
BenchmarkMutexLockUnlock-8              94345815                12.7 ns/op             0 B/op          0 allocs/op


Ou seja, a velocidade de trabalho é comparável ao RWMutex e Mutex usuais.



Código no github



All Articles