From 78ccbeac3be63d5ccec5b69a2556e8c01eb0c36a Mon Sep 17 00:00:00 2001 From: John Date: Tue, 18 Jun 2019 08:37:21 +0800 Subject: [PATCH] add Cas function for gtype; improve gmlock --- g/container/gtype/bool.go | 26 +++++++++++---- g/container/gtype/byte.go | 21 +++++++----- g/container/gtype/bytes.go | 14 ++++---- g/container/gtype/float32.go | 25 ++++++++------ g/container/gtype/float64.go | 23 ++++++++----- g/container/gtype/int.go | 21 +++++++----- g/container/gtype/int32.go | 21 +++++++----- g/container/gtype/int64.go | 21 +++++++----- g/container/gtype/interface.go | 14 ++++---- g/container/gtype/string.go | 14 ++++---- g/container/gtype/uint.go | 21 +++++++----- g/container/gtype/uint32.go | 21 +++++++----- g/container/gtype/uint64.go | 21 +++++++----- g/os/glog/glog.go | 6 ++-- g/os/gmlock/gmlock_mutex.go | 61 ++++++++++++++++------------------ geg/other/test.go | 46 ++++++++++++++++++++----- 16 files changed, 229 insertions(+), 147 deletions(-) diff --git a/g/container/gtype/bool.go b/g/container/gtype/bool.go index 1c5880141..802f292e0 100644 --- a/g/container/gtype/bool.go +++ b/g/container/gtype/bool.go @@ -29,21 +29,33 @@ func NewBool(value...bool) *Bool { } // Clone clones and returns a new concurrent-safe object for bool type. -func (t *Bool) Clone() *Bool { - return NewBool(t.Val()) +func (v *Bool) Clone() *Bool { + return NewBool(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Bool) Set(value bool) (old bool) { +func (v *Bool) Set(value bool) (old bool) { if value { - old = atomic.SwapInt32(&t.value, 1) == 1 + old = atomic.SwapInt32(&v.value, 1) == 1 } else { - old = atomic.SwapInt32(&t.value, 0) == 1 + old = atomic.SwapInt32(&v.value, 0) == 1 } return } // Val atomically loads t.valueue. -func (t *Bool) Val() bool { - return atomic.LoadInt32(&t.value) > 0 +func (v *Bool) Val() bool { + return atomic.LoadInt32(&v.value) > 0 } + +// Cas executes the compare-and-swap operation for value. +func (v *Bool) Cas(old, new bool) bool { + var oldInt32, newInt32 int32 + if old { + oldInt32 = 1 + } + if new { + newInt32 = 1 + } + return atomic.CompareAndSwapInt32(&v.value, oldInt32, newInt32) +} \ No newline at end of file diff --git a/g/container/gtype/byte.go b/g/container/gtype/byte.go index 14d92c7a0..5fa32ac0a 100644 --- a/g/container/gtype/byte.go +++ b/g/container/gtype/byte.go @@ -26,21 +26,26 @@ func NewByte(value...byte) *Byte { } // Clone clones and returns a new concurrent-safe object for byte type. -func (t *Byte) Clone() *Byte { - return NewByte(t.Val()) +func (v *Byte) Clone() *Byte { + return NewByte(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Byte) Set(value byte) (old byte) { - return byte(atomic.SwapInt32(&t.value, int32(value))) +func (v *Byte) Set(value byte) (old byte) { + return byte(atomic.SwapInt32(&v.value, int32(value))) } // Val atomically loads t.value. -func (t *Byte) Val() byte { - return byte(atomic.LoadInt32(&t.value)) +func (v *Byte) Val() byte { + return byte(atomic.LoadInt32(&v.value)) } // Add atomically adds to t.value and returns the new value. -func (t *Byte) Add(delta int) (new byte) { - return byte(atomic.AddInt32(&t.value, int32(delta))) +func (v *Byte) Add(delta byte) (new byte) { + return byte(atomic.AddInt32(&v.value, int32(delta))) +} + +// Cas executes the compare-and-swap operation for value. +func (v *Byte) Cas(old, new byte) bool { + return atomic.CompareAndSwapInt32(&v.value, int32(old), int32(new)) } diff --git a/g/container/gtype/bytes.go b/g/container/gtype/bytes.go index b36eecf05..a5784592f 100644 --- a/g/container/gtype/bytes.go +++ b/g/container/gtype/bytes.go @@ -23,21 +23,21 @@ func NewBytes(value...[]byte) *Bytes { } // Clone clones and returns a new concurrent-safe object for []byte type. -func (t *Bytes) Clone() *Bytes { - return NewBytes(t.Val()) +func (v *Bytes) Clone() *Bytes { + return NewBytes(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. // Note: The parameter cannot be nil. -func (t *Bytes) Set(value []byte) (old []byte) { - old = t.Val() - t.value.Store(value) +func (v *Bytes) Set(value []byte) (old []byte) { + old = v.Val() + v.value.Store(value) return } // Val atomically loads t.value. -func (t *Bytes) Val() []byte { - if s := t.value.Load(); s != nil { +func (v *Bytes) Val() []byte { + if s := v.value.Load(); s != nil { return s.([]byte) } return nil diff --git a/g/container/gtype/float32.go b/g/container/gtype/float32.go index 912c05d89..26661a465 100644 --- a/g/container/gtype/float32.go +++ b/g/container/gtype/float32.go @@ -28,27 +28,27 @@ func NewFloat32(value...float32) *Float32 { } // Clone clones and returns a new concurrent-safe object for float32 type. -func (t *Float32) Clone() *Float32 { - return NewFloat32(t.Val()) +func (v *Float32) Clone() *Float32 { + return NewFloat32(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Float32) Set(value float32) (old float32) { - return math.Float32frombits(atomic.SwapUint32(&t.value, math.Float32bits(value))) +func (v *Float32) Set(value float32) (old float32) { + return math.Float32frombits(atomic.SwapUint32(&v.value, math.Float32bits(value))) } // Val atomically loads t.value. -func (t *Float32) Val() float32 { - return math.Float32frombits(atomic.LoadUint32(&t.value)) +func (v *Float32) Val() float32 { + return math.Float32frombits(atomic.LoadUint32(&v.value)) } // Add atomically adds to t.value and returns the new value. -func (t *Float32) Add(delta float32) (new float32) { +func (v *Float32) Add(delta float32) (new float32) { for { - old := math.Float32frombits(t.value) + old := math.Float32frombits(v.value) new = old + delta if atomic.CompareAndSwapUint32( - (*uint32)(unsafe.Pointer(&t.value)), + (*uint32)(unsafe.Pointer(&v.value)), math.Float32bits(old), math.Float32bits(new), ) { @@ -56,4 +56,9 @@ func (t *Float32) Add(delta float32) (new float32) { } } return -} \ No newline at end of file +} + +// Cas executes the compare-and-swap operation for value. +func (v *Float32) Cas(old, new float32) bool { + return atomic.CompareAndSwapUint32(&v.value, uint32(old), uint32(new)) +} diff --git a/g/container/gtype/float64.go b/g/container/gtype/float64.go index b96419d01..c7f8968c6 100644 --- a/g/container/gtype/float64.go +++ b/g/container/gtype/float64.go @@ -28,27 +28,27 @@ func NewFloat64(value...float64) *Float64 { } // Clone clones and returns a new concurrent-safe object for float64 type. -func (t *Float64) Clone() *Float64 { - return NewFloat64(t.Val()) +func (v *Float64) Clone() *Float64 { + return NewFloat64(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Float64) Set(value float64) (old float64) { - return math.Float64frombits(atomic.SwapUint64(&t.value, math.Float64bits(value))) +func (v *Float64) Set(value float64) (old float64) { + return math.Float64frombits(atomic.SwapUint64(&v.value, math.Float64bits(value))) } // Val atomically loads t.value. -func (t *Float64) Val() float64 { - return math.Float64frombits(atomic.LoadUint64(&t.value)) +func (v *Float64) Val() float64 { + return math.Float64frombits(atomic.LoadUint64(&v.value)) } // Add atomically adds to t.value and returns the new value. -func (t *Float64) Add(delta float64) (new float64) { +func (v *Float64) Add(delta float64) (new float64) { for { - old := math.Float64frombits(t.value) + old := math.Float64frombits(v.value) new = old + delta if atomic.CompareAndSwapUint64( - (*uint64)(unsafe.Pointer(&t.value)), + (*uint64)(unsafe.Pointer(&v.value)), math.Float64bits(old), math.Float64bits(new), ) { @@ -57,3 +57,8 @@ func (t *Float64) Add(delta float64) (new float64) { } return } + +// Cas executes the compare-and-swap operation for value. +func (v *Float64) Cas(old, new float64) bool { + return atomic.CompareAndSwapUint64(&v.value, uint64(old), uint64(new)) +} diff --git a/g/container/gtype/int.go b/g/container/gtype/int.go index 270829e66..fa1717ba2 100644 --- a/g/container/gtype/int.go +++ b/g/container/gtype/int.go @@ -26,21 +26,26 @@ func NewInt(value...int) *Int { } // Clone clones and returns a new concurrent-safe object for int type. -func (t *Int) Clone() *Int { - return NewInt(t.Val()) +func (v *Int) Clone() *Int { + return NewInt(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Int) Set(value int) (old int) { - return int(atomic.SwapInt64(&t.value, int64(value))) +func (v *Int) Set(value int) (old int) { + return int(atomic.SwapInt64(&v.value, int64(value))) } // Val atomically loads t.value. -func (t *Int) Val() int { - return int(atomic.LoadInt64(&t.value)) +func (v *Int) Val() int { + return int(atomic.LoadInt64(&v.value)) } // Add atomically adds to t.value and returns the new value. -func (t *Int) Add(delta int) (new int) { - return int(atomic.AddInt64(&t.value, int64(delta))) +func (v *Int) Add(delta int) (new int) { + return int(atomic.AddInt64(&v.value, int64(delta))) +} + +// Cas executes the compare-and-swap operation for value. +func (v *Int) Cas(old, new int) bool { + return atomic.CompareAndSwapInt64(&v.value, int64(old), int64(new)) } \ No newline at end of file diff --git a/g/container/gtype/int32.go b/g/container/gtype/int32.go index 06517d8bb..3e1bcfad5 100644 --- a/g/container/gtype/int32.go +++ b/g/container/gtype/int32.go @@ -26,21 +26,26 @@ func NewInt32(value...int32) *Int32 { } // Clone clones and returns a new concurrent-safe object for int32 type. -func (t *Int32) Clone() *Int32 { - return NewInt32(t.Val()) +func (v *Int32) Clone() *Int32 { + return NewInt32(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Int32) Set(value int32) (old int32) { - return atomic.SwapInt32(&t.value, value) +func (v *Int32) Set(value int32) (old int32) { + return atomic.SwapInt32(&v.value, value) } // Val atomically loads t.value. -func (t *Int32) Val() int32 { - return atomic.LoadInt32(&t.value) +func (v *Int32) Val() int32 { + return atomic.LoadInt32(&v.value) } // Add atomically adds to t.value and returns the new value. -func (t *Int32) Add(delta int32) (new int32) { - return atomic.AddInt32(&t.value, delta) +func (v *Int32) Add(delta int32) (new int32) { + return atomic.AddInt32(&v.value, delta) +} + +// Cas executes the compare-and-swap operation for value. +func (v *Int32) Cas(old, new int32) bool { + return atomic.CompareAndSwapInt32(&v.value, old, new) } \ No newline at end of file diff --git a/g/container/gtype/int64.go b/g/container/gtype/int64.go index 38530dff4..9f2a35096 100644 --- a/g/container/gtype/int64.go +++ b/g/container/gtype/int64.go @@ -26,21 +26,26 @@ func NewInt64(value...int64) *Int64 { } // Clone clones and returns a new concurrent-safe object for int64 type. -func (t *Int64) Clone() *Int64 { - return NewInt64(t.Val()) +func (v *Int64) Clone() *Int64 { + return NewInt64(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Int64) Set(value int64) (old int64) { - return atomic.SwapInt64(&t.value, value) +func (v *Int64) Set(value int64) (old int64) { + return atomic.SwapInt64(&v.value, value) } // Val atomically loads t.value. -func (t *Int64) Val() int64 { - return atomic.LoadInt64(&t.value) +func (v *Int64) Val() int64 { + return atomic.LoadInt64(&v.value) } // Add atomically adds to t.value and returns the new value. -func (t *Int64) Add(delta int64) int64 { - return atomic.AddInt64(&t.value, delta) +func (v *Int64) Add(delta int64) int64 { + return atomic.AddInt64(&v.value, delta) +} + +// Cas executes the compare-and-swap operation for value. +func (v *Int64) Cas(old, new int64) bool { + return atomic.CompareAndSwapInt64(&v.value, old, new) } \ No newline at end of file diff --git a/g/container/gtype/interface.go b/g/container/gtype/interface.go index 0b510320e..6d70f2ac2 100644 --- a/g/container/gtype/interface.go +++ b/g/container/gtype/interface.go @@ -25,19 +25,19 @@ func NewInterface(value...interface{}) *Interface { } // Clone clones and returns a new concurrent-safe object for interface{} type. -func (t *Interface) Clone() *Interface { - return NewInterface(t.Val()) +func (v *Interface) Clone() *Interface { + return NewInterface(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. // Note: The parameter cannot be nil. -func (t *Interface) Set(value interface{}) (old interface{}) { - old = t.Val() - t.value.Store(value) +func (v *Interface) Set(value interface{}) (old interface{}) { + old = v.Val() + v.value.Store(value) return } // Val atomically loads t.value. -func (t *Interface) Val() interface{} { - return t.value.Load() +func (v *Interface) Val() interface{} { + return v.value.Load() } \ No newline at end of file diff --git a/g/container/gtype/string.go b/g/container/gtype/string.go index 390a0e507..2e1567a8a 100644 --- a/g/container/gtype/string.go +++ b/g/container/gtype/string.go @@ -25,20 +25,20 @@ func NewString(value...string) *String { } // Clone clones and returns a new concurrent-safe object for string type. -func (t *String) Clone() *String { - return NewString(t.Val()) +func (v *String) Clone() *String { + return NewString(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *String) Set(value string) (old string) { - old = t.Val() - t.value.Store(value) +func (v *String) Set(value string) (old string) { + old = v.Val() + v.value.Store(value) return } // Val atomically loads t.value. -func (t *String) Val() string { - s := t.value.Load() +func (v *String) Val() string { + s := v.value.Load() if s != nil { return s.(string) } diff --git a/g/container/gtype/uint.go b/g/container/gtype/uint.go index 3bc0b28ce..8f26024f6 100644 --- a/g/container/gtype/uint.go +++ b/g/container/gtype/uint.go @@ -26,21 +26,26 @@ func NewUint(value...uint) *Uint { } // Clone clones and returns a new concurrent-safe object for uint type. -func (t *Uint) Clone() *Uint { - return NewUint(t.Val()) +func (v *Uint) Clone() *Uint { + return NewUint(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Uint) Set(value uint) (old uint) { - return uint(atomic.SwapUint64(&t.value, uint64(value))) +func (v *Uint) Set(value uint) (old uint) { + return uint(atomic.SwapUint64(&v.value, uint64(value))) } // Val atomically loads t.value. -func (t *Uint) Val() uint { - return uint(atomic.LoadUint64(&t.value)) +func (v *Uint) Val() uint { + return uint(atomic.LoadUint64(&v.value)) } // Add atomically adds to t.value and returns the new value. -func (t *Uint) Add(delta uint) (new uint) { - return uint(atomic.AddUint64(&t.value, uint64(delta))) +func (v *Uint) Add(delta uint) (new uint) { + return uint(atomic.AddUint64(&v.value, uint64(delta))) +} + +// Cas executes the compare-and-swap operation for value. +func (v *Uint) Cas(old, new uint) bool { + return atomic.CompareAndSwapUint64(&v.value, uint64(old), uint64(new)) } \ No newline at end of file diff --git a/g/container/gtype/uint32.go b/g/container/gtype/uint32.go index 35db55b86..e39b070b1 100644 --- a/g/container/gtype/uint32.go +++ b/g/container/gtype/uint32.go @@ -26,21 +26,26 @@ func NewUint32(value...uint32) *Uint32 { } // Clone clones and returns a new concurrent-safe object for uint32 type. -func (t *Uint32) Clone() *Uint32 { - return NewUint32(t.Val()) +func (v *Uint32) Clone() *Uint32 { + return NewUint32(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Uint32) Set(value uint32) (old uint32) { - return atomic.SwapUint32(&t.value, value) +func (v *Uint32) Set(value uint32) (old uint32) { + return atomic.SwapUint32(&v.value, value) } // Val atomically loads t.value. -func (t *Uint32) Val() uint32 { - return atomic.LoadUint32(&t.value) +func (v *Uint32) Val() uint32 { + return atomic.LoadUint32(&v.value) } // Add atomically adds to t.value and returns the new value. -func (t *Uint32) Add(delta uint32) (new uint32) { - return atomic.AddUint32(&t.value, delta) +func (v *Uint32) Add(delta uint32) (new uint32) { + return atomic.AddUint32(&v.value, delta) +} + +// Cas executes the compare-and-swap operation for value. +func (v *Uint32) Cas(old, new uint32) bool { + return atomic.CompareAndSwapUint32(&v.value, old, new) } \ No newline at end of file diff --git a/g/container/gtype/uint64.go b/g/container/gtype/uint64.go index 923068398..ce8b42f90 100644 --- a/g/container/gtype/uint64.go +++ b/g/container/gtype/uint64.go @@ -26,21 +26,26 @@ func NewUint64(value...uint64) *Uint64 { } // Clone clones and returns a new concurrent-safe object for uint64 type. -func (t *Uint64) Clone() *Uint64 { - return NewUint64(t.Val()) +func (v *Uint64) Clone() *Uint64 { + return NewUint64(v.Val()) } // Set atomically stores into t.value and returns the previous value of t.value. -func (t *Uint64) Set(value uint64) (old uint64) { - return atomic.SwapUint64(&t.value, value) +func (v *Uint64) Set(value uint64) (old uint64) { + return atomic.SwapUint64(&v.value, value) } // Val atomically loads t.value. -func (t *Uint64) Val() uint64 { - return atomic.LoadUint64(&t.value) +func (v *Uint64) Val() uint64 { + return atomic.LoadUint64(&v.value) } // Add atomically adds to t.value and returns the new value. -func (t *Uint64) Add(delta uint64) (new uint64) { - return atomic.AddUint64(&t.value, delta) +func (v *Uint64) Add(delta uint64) (new uint64) { + return atomic.AddUint64(&v.value, delta) +} + +// Cas executes the compare-and-swap operation for value. +func (v *Uint64) Cas(old, new uint64) bool { + return atomic.CompareAndSwapUint64(&v.value, old, new) } \ No newline at end of file diff --git a/g/os/glog/glog.go b/g/os/glog/glog.go index 45f39011f..a3d6d3b51 100644 --- a/g/os/glog/glog.go +++ b/g/os/glog/glog.go @@ -37,8 +37,8 @@ func init() { } // SetPath sets the directory path for file logging. -func SetPath(path string) { - logger.SetPath(path) +func SetPath(path string) error { + return logger.SetPath(path) } // GetPath returns the logging directory path for file logging. @@ -89,7 +89,7 @@ func SetAsync(enabled bool) { logger.SetAsync(enabled) } -// SetStdoutPrint sets whether ouptput the logging contents to stdout, which is false in default. +// SetStdoutPrint sets whether ouptput the logging contents to stdout, which is true in default. func SetStdoutPrint(enabled bool) { logger.SetStdoutPrint(enabled) } diff --git a/g/os/gmlock/gmlock_mutex.go b/g/os/gmlock/gmlock_mutex.go index 4faf7b82d..6fcd261f7 100644 --- a/g/os/gmlock/gmlock_mutex.go +++ b/g/os/gmlock/gmlock_mutex.go @@ -15,17 +15,18 @@ import ( // It wraps the sync.RWMutex to implements more rich features. type Mutex struct { mu sync.RWMutex - wid *gtype.Int64 // Unique id, used for multiple safely Unlock. - rcount *gtype.Int // Reading locks count. - wcount *gtype.Int // Writing locks count. + wid *gtype.Int64 // Unique id, used for multiple safely Unlock. + number *gtype.Int // Locking number. + // 0: writing lock false; + // 1: writing lock true; + // >=2: reading lock; } // NewMutex creates and returns a new mutex. func NewMutex() *Mutex { return &Mutex{ wid : gtype.NewInt64(), - rcount : gtype.NewInt(), - wcount : gtype.NewInt(), + number : gtype.NewInt(), } } @@ -33,29 +34,25 @@ func NewMutex() *Mutex { // If the lock is already locked for reading or writing, // Lock blocks until the lock is available. func (m *Mutex) Lock() { - m.wcount.Add(1) - m.mu.Lock() - m.wid.Add(1) + m.mu.Lock() + m.number.Set(1) + m.wid.Add(1) } // Unlock unlocks the write lock. // It is safe to be called multiple times. func (m *Mutex) Unlock() { - if m.wcount.Val() > 0 { - if m.wcount.Add(-1) >= 0 { - m.mu.Unlock() - } else { - m.wcount.Add(1) - } - } + if m.number.Cas(1, 0) { + m.mu.Unlock() + } } // RLock locks mutex for reading. // If the mutex is already locked for writing, // It blocks until the lock is available. func (m *Mutex) RLock() { - m.rcount.Add(1) m.mu.RLock() + m.number.Add(2) } // RUnlock undoes a single RLock call; @@ -64,11 +61,11 @@ func (m *Mutex) RLock() { // on entry to RUnlock. // It is safe to be called multiple times. func (m *Mutex) RUnlock() { - if m.rcount.Val() > 0 { - if m.rcount.Add(-1) >= 0 { + if n := m.number.Val(); n >= 2 && n%2 == 0 { + if n := m.number.Add(-2); n >= 0 && n%2 == 0 { m.mu.RUnlock() } else { - m.rcount.Add(1) + m.number.Add(2) } } } @@ -77,27 +74,25 @@ func (m *Mutex) RUnlock() { // It returns true if success, or if there's a write/read lock on the mutex, // it returns false. func (m *Mutex) TryLock() bool { - // The first check, but it cannot ensure the atomicity. - if m.wcount.Val() == 0 && m.rcount.Val() == 0 { - // The second check, it ensures the atomicity with atomic Add. - if m.wcount.Add(1) == 1 { - m.mu.Lock() - m.wid.Add(1) - return true - } else { - m.wcount.Add(-1) - } + if m.number.Cas(0, 1) { + m.mu.Lock() + m.wid.Add(1) + return true } return false } // TryRLock tries locking the mutex for reading. -// It returns true if success, or if there's a write lock on the mutex, it returns false. +// It returns true if success, or if there's a writing lock on the mutex, it returns false. +// +// Note that it's not an atomic operation. +// +// TODO It should be an atomic operation. func (m *Mutex) TryRLock() bool { - // There must be no write lock on mutex. - if m.wcount.Val() == 0 { - m.rcount.Add(1) + // There must be no writing lock on the mutex. + if n := m.number.Val(); n%2 == 0 { m.mu.RLock() + m.number.Add(2) return true } return false diff --git a/geg/other/test.go b/geg/other/test.go index 7e31feb29..6ef91fe4c 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,15 +1,45 @@ package main import ( - "fmt" - "sync" + "github.com/gogf/gf/g/container/garray" + "github.com/gogf/gf/g/os/gmlock" + "github.com/gogf/gf/g/test/gtest" + "time" ) func main() { - wg := sync.WaitGroup{} - wg.Add(1) - wg.Add(-100) - wg.Add() - wg.Wait() - fmt.Println(1) + mu := gmlock.NewMutex() + array := garray.New() + go func() { + mu.LockFunc(func() { + array.Append(1) + time.Sleep(100 * time.Millisecond) + }) + }() + go func() { + time.Sleep(50 * time.Millisecond) + mu.LockFunc(func() { + array.Append(1) + }) + }() + go func() { + time.Sleep(50 * time.Millisecond) + mu.LockFunc(func() { + array.Append(1) + }) + }() + + go func() { + time.Sleep(60 * time.Millisecond) + mu.Unlock() + mu.Unlock() + mu.Unlock() + }() + + time.Sleep(20 * time.Millisecond) + gtest.Assert(array.Len(), 1) + time.Sleep(50 * time.Millisecond) + gtest.Assert(array.Len(), 1) + time.Sleep(50 * time.Millisecond) + gtest.Assert(array.Len(), 3) }