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