Hallo!
In diesem Artikel möchte ich Ihnen sagen, wie Sie Ihren eigenen RWMutex erstellen können, aber mit der Möglichkeit, das Zeitlimit zu überschreiten oder den Kontext auszulösen, um die Sperre zu überspringen. Implementieren Sie also TryLock (context.Context) und RTryLock (context.Context), jedoch für Ihren eigenen Mutex.

Das Bild zeigt, wie man Wasser in einen sehr schmalen Hals gießt.
Zunächst sollte klargestellt werden, dass für 99% der Aufgaben solche Methoden überhaupt nicht benötigt werden. Sie werden benötigt, wenn die blockierte Ressource möglicherweise längere Zeit nicht freigegeben wird. Ich möchte darauf hinweisen, dass es sich lohnt, die Logik am Anfang so zu optimieren, dass die Blockierungszeit minimiert wird, wenn eine blockierte Ressource lange Zeit beschäftigt bleibt.
Weitere Informationen finden Sie unter Tanzen mit Mutexen in Go in Beispiel 2.
Wenn wir jedoch einen Ressourcenfluss lange beibehalten müssen, scheint es mir schwierig zu sein, auf TryLock zu verzichten.
, , 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)
}
Wie Sie sehen können, kann der Mutex, wenn er nicht gesperrt ist, einfach blockiert werden. Wenn nicht, werden wir zu einem komplexeren Schema übergehen.
Zu Beginn erhalten wir den Kanal und gehen in eine Endlosschleife. Wenn sich herausstellt, dass er gesperrt ist, beenden wir ihn mit Erfolg. Wenn nicht, warten wir auf eines der beiden Ereignisse oder darauf, dass der Kanal entsperrt ist oder dass der Stream ctx.Done () entsperrt wird:
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
}
}
}
Lassen Sie uns den Mutex entsperren.
Wir müssen den Status ändern und gegebenenfalls den Kanal entsperren.
Wie ich oben geschrieben habe, überspringt case <-ch den Ausführungsfluss weiter, wenn der Kanal geschlossen ist.
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")
}
Der Mutex selbst ist fertig, Sie müssen einige Tests und Standardmethoden wie Lock () und RLock () dafür schreiben
Benchmarks an meinem Auto zeigten diese Geschwindigkeiten
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
Das heißt, die Arbeitsgeschwindigkeit ist vergleichbar mit der üblichen RWMutex und Mutex.