Essayons de verrouiller dans Go (context.Context)

salut!



Dans cet article, j'aimerais vous dire comment vous pouvez créer votre propre RWMutex, mais avec la possibilité de temporiser ou de déclencher le contexte pour ignorer le verrou. Autrement dit, implémentez TryLock (context.Context) et RTryLock (context.Context), mais pour votre propre Mutex.



image



L'image montre comment verser de l'eau dans un col très étroit.



Pour commencer, il convient de préciser que pour 99% des tâches, ces méthodes ne sont pas du tout nécessaires. Ils sont nécessaires lorsque la ressource bloquée peut ne pas être libérée pendant très longtemps. Je tiens à noter que si la ressource bloquée reste occupée pendant une longue période, vous devez d'abord essayer d'optimiser la logique de manière à minimiser le temps de blocage.



Pour plus d'informations, consultez Danser avec des mutex dans Go dans l'exemple 2.



Mais si, néanmoins, nous devons avoir une longue rétention d'un flux de ressources, alors il me semble que TryLock sera difficile à se passer.



, , 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)
}


Comme vous pouvez le voir, si le Mutex n'est pas verrouillé, il peut être simplement bloqué, mais sinon, nous passerons à un schéma plus complexe.



Au début, nous obtenons un canal, et nous entrons dans une boucle sans fin, s'il s'avère être verrouillé, nous sortons avec succès, et sinon, nous commençons à attendre l'un des 2 événements, ou que le canal soit débloqué, ou que le flux ctx.Done () se débloque:



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
        }
    }
}


Débloquons le mutex.



Nous devons changer l'état et, si nécessaire, débloquer le canal.



Comme je l'ai écrit ci-dessus, si le canal est fermé, case <-ch sautera davantage le flux d'exécution.



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")
}


Le mutex lui-même est prêt, vous devez écrire quelques tests et méthodes standard comme Lock () et RLock () pour cela



Les repères sur ma voiture ont montré ces vitesses



  
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


Autrement dit, la vitesse de travail est comparable à celle des RWMutex et Mutex habituels.



Code sur github



All Articles