Ayo coba Kunci di Go (konteks.Context)

Halo!



Pada artikel ini, saya ingin memberi tahu Anda bagaimana Anda bisa membuat RWMutex Anda sendiri, tetapi dengan kemampuan untuk melewati kunci dengan batas waktu atau dengan memicu konteks. Artinya, terapkan TryLock (konteks.Context) dan RTryLock (konteks.Context), tetapi untuk Mutex Anda sendiri.



gambar



Gambar menunjukkan cara menuangkan air ke leher yang sangat sempit.



Untuk memulainya, harus diklarifikasi bahwa untuk 99% tugas, metode seperti itu tidak diperlukan sama sekali. Mereka dibutuhkan ketika sumber daya yang diblokir mungkin tidak dirilis untuk waktu yang sangat lama. Saya ingin mencatat bahwa jika sumber daya yang diblokir tetap sibuk untuk waktu yang lama, Anda harus terlebih dahulu mencoba mengoptimalkan logika sedemikian rupa untuk meminimalkan waktu pemblokiran.



Untuk informasi lebih lanjut, lihat Menari dengan Mutex di Go di Contoh 2.



Namun, jika, bagaimanapun, kita harus memiliki retensi lama dari satu aliran sumber daya, maka menurut saya TryLock akan sulit dilakukan tanpanya.



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


Seperti yang Anda lihat, jika Mutex tidak dikunci, maka Mutex dapat dengan mudah diblokir, tetapi jika tidak, maka kita akan beralih ke skema yang lebih kompleks.



Pada awalnya, kami mendapatkan saluran, dan kami masuk ke loop tak terbatas, jika ternyata terkunci, kami keluar dengan sukses, dan jika tidak, maka kami mulai menunggu salah satu dari 2 acara, atau saluran tersebut tidak diblokir, atau aliran ctx.Done () akan membuka blokir:



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


Mari buka blokir mutex.



Kami perlu mengubah status dan, jika perlu, membuka blokir saluran.



Seperti yang saya tulis di atas, jika saluran ditutup, maka case <-ch akan melewatkan aliran eksekusi lebih jauh.



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


Mutex itu sendiri sudah siap, Anda perlu menulis beberapa tes dan metode standar seperti Lock () dan RLock () untuk itu



Tolok ukur di mobil saya menunjukkan kecepatan ini



  
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


Artinya, kecepatan kerjanya sebanding dengan RWMutex dan Mutex biasa.



Kode di github



All Articles