مرحبا!
في هذه المقالة ، أود أن أخبرك كيف يمكنك إنشاء 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 المعتاد.