لنفعل TryLock (Context.Context) في Go

مرحبا!



في هذه المقالة ، أود أن أخبرك كيف يمكنك إنشاء RWMutex الخاص بك ، ولكن مع القدرة على المهلة أو تشغيل السياق لتخطي القفل. أي ، قم بتنفيذ TryLock (Context.Context) و RTryLock (Context.Context) ، ولكن من أجل Mutex الخاص بك.



صورة



توضح الصورة كيفية صب الماء في عنق ضيق للغاية.



بادئ ذي بدء ، يجب توضيح أنه بالنسبة لـ 99٪ من المهام ، هذه الأساليب غير مطلوبة على الإطلاق. تصبح ضرورية عندما لا يتم تحرير المورد المحظور لفترة طويلة جدًا. أريد أن أشير إلى أنه إذا ظل المورد المحظور مشغولاً لفترة طويلة ، فيجب عليك أولاً محاولة تحسين المنطق بطريقة تقلل من وقت الحظر.



لمزيد من المعلومات ، راجع الرقص باستخدام كائنات متعددة في Go في المثال 2.



ولكن ، مع ذلك ، إذا كان علينا الاحتفاظ لفترة طويلة بتدفق مورد واحد ، فيبدو لي أن 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)
}


كما ترى ، إذا لم يتم قفل Mutex ، فيمكن حظره ببساطة ، ولكن إذا لم يكن كذلك ، فسننتقل إلى مخطط أكثر تعقيدًا.



في البداية ، نحصل على قناة ، وندخل في حلقة لا نهاية لها ، إذا تبين أنها مغلقة ، فإننا نخرج بنجاح ، وإذا لم يكن الأمر كذلك ، فإننا نبدأ في انتظار أحد الحدثين ، أو أن القناة غير محظورة ، أو أن ctx.



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


دعونا نقوم بإلغاء قفل كائن المزامنة.



نحن بحاجة إلى تغيير الحالة ، وإذا لزم الأمر ، إلغاء حظر القناة.



كما كتبت أعلاه ، إذا كانت القناة مغلقة ، فستتخطى الحالة <-ch تدفق التنفيذ أكثر.



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


كائن المزامنة نفسه جاهز ، تحتاج إلى كتابة اختبارين وطرق قياسية مثل Lock () و RLock () له



أظهرت المعايير على سيارتي هذه السرعات



  
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


أي أن سرعة العمل قابلة للمقارنة مع RWMutex و Mutex المعتاد.



كود على جيثب



All Articles