初始化项目
This commit is contained in:
90
trunk/goutil/syncUtil/locker.go
Normal file
90
trunk/goutil/syncUtil/locker.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 写锁对象
|
||||
type Locker struct {
|
||||
locking bool
|
||||
prevStack []byte
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// 内部锁
|
||||
// 返回值:
|
||||
// 加锁是否成功
|
||||
func (this *Locker) lock() bool {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
|
||||
// 如果已经被锁定,则返回失败
|
||||
if this.locking {
|
||||
return false
|
||||
}
|
||||
|
||||
// 否则进行锁定,并返回成功
|
||||
this.locking = true
|
||||
|
||||
// 记录Stack信息
|
||||
if if_record_stack_info {
|
||||
this.prevStack = debug.Stack()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 尝试加锁,如果在指定的时间内失败,则会返回失败;否则返回成功
|
||||
// timeout:指定的毫秒数,timeout<=0则将会死等
|
||||
// 返回值:
|
||||
// 成功或失败
|
||||
// 如果失败,返回上一次成功加锁时的堆栈信息
|
||||
// 如果失败,返回当前的堆栈信息
|
||||
func (this *Locker) Lock(timeout int) (successful bool, prevStack string, currStack string) {
|
||||
timeout = getTimeout(timeout)
|
||||
|
||||
// 遍历指定的次数(即指定的超时时间)
|
||||
for i := 0; i < timeout; i++ {
|
||||
// 如果锁定成功,则返回成功
|
||||
if this.lock() {
|
||||
successful = true
|
||||
break
|
||||
}
|
||||
|
||||
// 如果锁定失败,则休眠1ms,然后再重试
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
// 如果时间结束仍然是失败,则返回上次成功的堆栈信息,以及当前的堆栈信息
|
||||
if successful == false {
|
||||
if this.prevStack != nil && len(this.prevStack) > 0 {
|
||||
prevStack = string(this.prevStack)
|
||||
}
|
||||
currStack = string(debug.Stack())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 锁定(死等方式)
|
||||
func (this *Locker) WaitLock() {
|
||||
successful, prevStack, currStack := this.Lock(0)
|
||||
if successful == false {
|
||||
fmt.Printf("Locker.WaitLock():{PrevStack:%s, currStack:%s}\n", prevStack, currStack)
|
||||
}
|
||||
}
|
||||
|
||||
// 解锁
|
||||
func (this *Locker) Unlock() {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
this.locking = false
|
||||
}
|
||||
|
||||
// 创建新的锁对象
|
||||
func NewLocker() *Locker {
|
||||
return &Locker{}
|
||||
}
|
||||
58
trunk/goutil/syncUtil/lockerUtil.go
Normal file
58
trunk/goutil/syncUtil/lockerUtil.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 锁工具类
|
||||
type LockerUtil struct {
|
||||
// 锁集合
|
||||
lockerMap map[string]*Locker
|
||||
|
||||
// 锁对象
|
||||
rwMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// 创建新的锁工具类
|
||||
func NewLockerUtil() *LockerUtil {
|
||||
return &LockerUtil{
|
||||
lockerMap: make(map[string]*Locker, 8),
|
||||
}
|
||||
}
|
||||
|
||||
// 获取锁对象
|
||||
// lockName:锁名
|
||||
// 返回值:
|
||||
// *Locker:锁对象
|
||||
func (this *LockerUtil) GetLock(lockName string) *Locker {
|
||||
var lockerObj *Locker
|
||||
var exists bool
|
||||
|
||||
func() {
|
||||
this.rwMutex.RLock()
|
||||
defer this.rwMutex.RUnlock()
|
||||
lockerObj, exists = this.lockerMap[lockName]
|
||||
}()
|
||||
if exists {
|
||||
return lockerObj
|
||||
}
|
||||
|
||||
this.rwMutex.Lock()
|
||||
defer this.rwMutex.Unlock()
|
||||
|
||||
lockerObj, exists = this.lockerMap[lockName]
|
||||
if !exists {
|
||||
lockerObj = NewLocker()
|
||||
this.lockerMap[lockName] = lockerObj
|
||||
}
|
||||
|
||||
return lockerObj
|
||||
}
|
||||
|
||||
// 释放锁对象
|
||||
// lockName:锁名
|
||||
func (this *LockerUtil) ReleaseLock(lockName string) {
|
||||
this.rwMutex.Lock()
|
||||
defer this.rwMutex.Unlock()
|
||||
delete(this.lockerMap, lockName)
|
||||
}
|
||||
27
trunk/goutil/syncUtil/lockerUtil_test.go
Normal file
27
trunk/goutil/syncUtil/lockerUtil_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
lockerUtilObj = NewLockerUtil()
|
||||
)
|
||||
|
||||
func LockerUtil_TestGetLock(t *testing.T) {
|
||||
count := 100
|
||||
for i := 1; i <= count; i++ {
|
||||
lockerUtilObj.GetLock(fmt.Sprintf("%d", i))
|
||||
if lockerCount := len(lockerUtilObj.lockerMap); lockerCount != i {
|
||||
t.Errorf("(GetLock)Expected %d locker, but now got: %d", count, lockerCount)
|
||||
}
|
||||
}
|
||||
|
||||
for i := count; i > 0; i-- {
|
||||
lockerUtilObj.ReleaseLock(fmt.Sprintf("%d", i))
|
||||
if lockerCount := len(lockerUtilObj.lockerMap); lockerCount != i-1 {
|
||||
t.Errorf("(ReleaseLock)Expected %d locker, but now got: %d", count, lockerCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
98
trunk/goutil/syncUtil/locker_test.go
Normal file
98
trunk/goutil/syncUtil/locker_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewLocker1(t *testing.T) {
|
||||
count := 1000000
|
||||
succeedCount := 0
|
||||
expected := 1000000
|
||||
goroutineCount := 1
|
||||
|
||||
lockerObj := NewLocker()
|
||||
ch := make(chan bool, goroutineCount)
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
go lockerTest(lockerObj, &succeedCount, count/goroutineCount, ch)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
<-ch
|
||||
}
|
||||
|
||||
if succeedCount != expected {
|
||||
t.Errorf("Expected %d, but got %d", expected, succeedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLocker2(t *testing.T) {
|
||||
count := 1000000
|
||||
succeedCount := 0
|
||||
expected := 1000000
|
||||
goroutineCount := 100
|
||||
|
||||
lockerObj := NewLocker()
|
||||
ch := make(chan bool, goroutineCount)
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
go lockerTest(lockerObj, &succeedCount, count/goroutineCount, ch)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
<-ch
|
||||
}
|
||||
|
||||
if succeedCount != expected {
|
||||
t.Errorf("Expected %d, but got %d", expected, succeedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLocker3(t *testing.T) {
|
||||
count := 1000000
|
||||
succeedCount := 0
|
||||
expected := 1000000
|
||||
goroutineCount := 10000
|
||||
|
||||
lockerObj := NewLocker()
|
||||
ch := make(chan bool, goroutineCount)
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
go lockerTest(lockerObj, &succeedCount, count/goroutineCount, ch)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
<-ch
|
||||
}
|
||||
|
||||
if succeedCount != expected {
|
||||
t.Errorf("Expected %d, but got %d", expected, succeedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLocker4(t *testing.T) {
|
||||
lockerObj := NewLocker()
|
||||
if successful, _, _ := lockerObj.Lock(100); successful == false {
|
||||
t.Errorf("Lock should be successful, but now it fails.")
|
||||
}
|
||||
|
||||
if successful, _, _ := lockerObj.Lock(100); successful {
|
||||
t.Errorf("Lock should be failed, but now it succeeds.")
|
||||
}
|
||||
}
|
||||
|
||||
func lockerTest(lockerObj *Locker, succeedCount *int, count int, ch chan bool) {
|
||||
if success, _, _ := lockerObj.Lock(10000); !success {
|
||||
fmt.Printf("[%v]获取锁超时\n", time.Now())
|
||||
return
|
||||
}
|
||||
defer lockerObj.Unlock()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
*succeedCount += 1
|
||||
}
|
||||
|
||||
ch <- true
|
||||
}
|
||||
171
trunk/goutil/syncUtil/mutex.go
Normal file
171
trunk/goutil/syncUtil/mutex.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Set the behavier on unlock unlocked mutex
|
||||
var PanicOnBug = true
|
||||
|
||||
// another mutex implementation with TryLock method
|
||||
type Mutex interface {
|
||||
Lock()
|
||||
UnLock()
|
||||
// TryLock return true if it fetch mutex
|
||||
TryLock() bool
|
||||
// TryLockTimeout return true if it fetch mutex, return false if timeout
|
||||
TryLockTimeout(timeout time.Duration) bool
|
||||
// TryLockTimeout return true if it fetch mutex, return false if context done
|
||||
TryLockContext(ctx context.Context) bool
|
||||
}
|
||||
|
||||
func NewMutex() Mutex {
|
||||
m := &mutex{ch: make(chan struct{}, 1)}
|
||||
m.ch <- struct{}{}
|
||||
return m
|
||||
}
|
||||
|
||||
type mutex struct {
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
func (m *mutex) Lock() {
|
||||
<-m.ch
|
||||
}
|
||||
|
||||
func (m *mutex) UnLock() {
|
||||
select {
|
||||
case m.ch <- struct{}{}:
|
||||
default:
|
||||
if PanicOnBug {
|
||||
panic("unlock of unlocked mutex")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mutex) TryLock() bool {
|
||||
select {
|
||||
case <-m.ch:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *mutex) TryLockTimeout(timeout time.Duration) bool {
|
||||
tm := time.NewTimer(timeout)
|
||||
select {
|
||||
case <-m.ch:
|
||||
tm.Stop()
|
||||
return true
|
||||
case <-tm.C:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *mutex) TryLockContext(ctx context.Context) bool {
|
||||
select {
|
||||
case <-m.ch:
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type MutexGroup interface {
|
||||
Lock(i interface{})
|
||||
UnLock(i interface{})
|
||||
UnLockAndFree(i interface{})
|
||||
// TryLock return true if it fetch mutex
|
||||
TryLock(i interface{}) bool
|
||||
// TryLockTimeout return true if it fetch mutex, return false if timeout
|
||||
TryLockTimeout(i interface{}, timeout time.Duration) bool
|
||||
// TryLockTimeout return true if it fetch mutex, return false if context done
|
||||
TryLockContext(i interface{}, ctx context.Context) bool
|
||||
}
|
||||
|
||||
func NewMutexGroup() MutexGroup {
|
||||
return &mutexGroup{group: make(map[interface{}]*entry)}
|
||||
}
|
||||
|
||||
type mutexGroup struct {
|
||||
mu sync.Mutex
|
||||
group map[interface{}]*entry
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
ref int
|
||||
mu Mutex
|
||||
}
|
||||
|
||||
func (m *mutexGroup) get(i interface{}, ref int) Mutex {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
en, ok := m.group[i]
|
||||
if !ok {
|
||||
if ref > 0 {
|
||||
en = &entry{mu: NewMutex()}
|
||||
m.group[i] = en
|
||||
} else if PanicOnBug {
|
||||
panic("unlock of unlocked mutex")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
en.ref += ref
|
||||
return en.mu
|
||||
}
|
||||
|
||||
func (m *mutexGroup) Lock(i interface{}) {
|
||||
m.get(i, 1).Lock()
|
||||
}
|
||||
|
||||
func (m *mutexGroup) UnLock(i interface{}) {
|
||||
mu := m.get(i, -1)
|
||||
if mu != nil {
|
||||
mu.UnLock()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mutexGroup) UnLockAndFree(i interface{}) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
en, ok := m.group[i]
|
||||
if !ok {
|
||||
if PanicOnBug {
|
||||
panic("unlock of unlocked mutex")
|
||||
}
|
||||
return
|
||||
}
|
||||
en.ref--
|
||||
if en.ref == 0 {
|
||||
delete(m.group, i)
|
||||
}
|
||||
en.mu.UnLock()
|
||||
}
|
||||
|
||||
func (m *mutexGroup) TryLock(i interface{}) bool {
|
||||
locked := m.get(i, 1).TryLock()
|
||||
if !locked {
|
||||
m.get(i, -1)
|
||||
}
|
||||
return locked
|
||||
}
|
||||
|
||||
func (m *mutexGroup) TryLockTimeout(i interface{}, timeout time.Duration) bool {
|
||||
locked := m.get(i, 1).TryLockTimeout(timeout)
|
||||
if !locked {
|
||||
m.get(i, -1)
|
||||
}
|
||||
return locked
|
||||
}
|
||||
|
||||
func (m *mutexGroup) TryLockContext(i interface{}, ctx context.Context) bool {
|
||||
locked := m.get(i, 1).TryLockContext(ctx)
|
||||
if !locked {
|
||||
m.get(i, -1)
|
||||
}
|
||||
return locked
|
||||
}
|
||||
225
trunk/goutil/syncUtil/mutex_test.go
Normal file
225
trunk/goutil/syncUtil/mutex_test.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMutex(t *testing.T) {
|
||||
mu := NewMutex()
|
||||
mu.Lock()
|
||||
defer mu.UnLock()
|
||||
if mu.TryLock() {
|
||||
t.Errorf("cannot fetch mutex !!!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMutexTryLockTimeout(t *testing.T) {
|
||||
fmt.Println("start")
|
||||
mu := NewMutex()
|
||||
mu.Lock()
|
||||
go func() {
|
||||
time.Sleep(20 * time.Second)
|
||||
mu.UnLock()
|
||||
}()
|
||||
// if !mu.TryLockTimeout(500 * time.Microsecond) {
|
||||
// t.Errorf("cannot fetch mutex in 500us !!!")
|
||||
// }
|
||||
if !mu.TryLockTimeout(15 * time.Second) {
|
||||
t.Errorf("should fetch mutex in 5ms !!!")
|
||||
}
|
||||
mu.UnLock()
|
||||
}
|
||||
|
||||
func TestMutexUnlockTwice(t *testing.T) {
|
||||
mu := NewMutex()
|
||||
mu.Lock()
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
if x != "unlock of unlocked mutex" {
|
||||
t.Errorf("unexpect panic")
|
||||
}
|
||||
} else {
|
||||
t.Errorf("should panic after unlock twice")
|
||||
}
|
||||
}()
|
||||
mu.UnLock()
|
||||
mu.UnLock()
|
||||
}
|
||||
|
||||
func TestMutexTryLockContext(t *testing.T) {
|
||||
mu := NewMutex()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
mu.Lock()
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
if mu.TryLockContext(ctx) {
|
||||
t.Errorf("cannot fetch mutex !!!")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMutex(b *testing.B) {
|
||||
mu := NewMutex()
|
||||
a := 0
|
||||
c := 0
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
mu.Lock()
|
||||
a++
|
||||
mu.UnLock()
|
||||
mu.Lock()
|
||||
c = a
|
||||
mu.UnLock()
|
||||
}
|
||||
})
|
||||
_ = a
|
||||
_ = c
|
||||
}
|
||||
|
||||
func TestMutexGroup(t *testing.T) {
|
||||
mu := NewMutexGroup()
|
||||
mu.Lock("g")
|
||||
defer mu.UnLock("g")
|
||||
if mu.TryLock("g") {
|
||||
t.Errorf("cannot fetch mutex !!!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMutexGroupMutliWaitLock(t *testing.T) {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu = NewMutexGroup()
|
||||
cn = 3
|
||||
)
|
||||
|
||||
for i := 0; i < cn; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
mu.Lock("h")
|
||||
time.Sleep(1e7)
|
||||
mu.UnLock("h")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i := 0; i < cn; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
mu.Lock("g")
|
||||
time.Sleep(1e7)
|
||||
mu.UnLockAndFree("g")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestMutexGroupUnLockAndFree(t *testing.T) {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu = NewMutexGroup()
|
||||
mg = mu.(*mutexGroup)
|
||||
)
|
||||
|
||||
for j := 1; j < 5; j++ {
|
||||
for i := 0; i < j; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
mu.Lock("h")
|
||||
time.Sleep(1e6)
|
||||
mu.UnLockAndFree("h")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
mg.mu.Lock()
|
||||
if _, ok := mg.group["h"]; ok {
|
||||
t.Error("h mutex exist after UnLockAndFree")
|
||||
}
|
||||
mg.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMutexGroupTryLockFailedAndUnLockAndFree(t *testing.T) {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu = NewMutexGroup()
|
||||
mg = mu.(*mutexGroup)
|
||||
)
|
||||
|
||||
for j := 1; j < 5; j++ {
|
||||
for i := 0; i < j; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if mu.TryLock("h") {
|
||||
time.Sleep(1e6)
|
||||
mu.UnLockAndFree("h")
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
mg.mu.Lock()
|
||||
if _, ok := mg.group["h"]; ok {
|
||||
t.Error("h mutex exist after UnLockAndFree")
|
||||
}
|
||||
mg.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMutexGroupTryLockTimeout(t *testing.T) {
|
||||
mu := NewMutexGroup()
|
||||
mu.Lock("g")
|
||||
go func() {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
mu.UnLock("g")
|
||||
}()
|
||||
if mu.TryLockTimeout("g", 500*time.Microsecond) {
|
||||
t.Errorf("cannot fetch mutex in 500us !!!")
|
||||
}
|
||||
if !mu.TryLockTimeout("g", 5*time.Millisecond) {
|
||||
t.Errorf("should fetch mutex in 5ms !!!")
|
||||
}
|
||||
mu.UnLock("g")
|
||||
}
|
||||
|
||||
func TestMutexGroupTryLockContext(t *testing.T) {
|
||||
mu := NewMutexGroup()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
mu.Lock("g")
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
if mu.TryLockContext("g", ctx) {
|
||||
t.Errorf("cannot fetch mutex !!!")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMutexGroup(b *testing.B) {
|
||||
mu := NewMutexGroup()
|
||||
a := 0
|
||||
c := 0
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
mu.Lock("g")
|
||||
a++
|
||||
mu.UnLock("g")
|
||||
mu.Lock("g")
|
||||
c = a
|
||||
mu.UnLock("g")
|
||||
}
|
||||
})
|
||||
_ = a
|
||||
_ = c
|
||||
}
|
||||
178
trunk/goutil/syncUtil/rwLocker.go
Normal file
178
trunk/goutil/syncUtil/rwLocker.go
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
通过在RWLocker对象中引入writeProtectEndTime(写锁保护结束时间),来提高获取写锁的成功率。
|
||||
当写锁获取失败时,就设置一个写锁保护结束时间,在这段时间内,只允许写锁进行获取,而读锁的获取请求会被拒绝。
|
||||
通过重置写锁保护结束时间的时机,对写锁的优先级程度进行调整。有两个重置写锁保护结束时间的时机:
|
||||
1、在成功获取到写锁时:此时重置,有利于下一个写锁需求者在当前写锁持有者处理逻辑时设置保护时间,从而当当前写锁持有者释放锁时,下一个写锁需求者可以立刻获得写锁;
|
||||
2、在写锁解锁时:此时重置,给了读锁和写锁的需求者同样的机会进行锁的竞争机会;
|
||||
综上:RWLocker可以提供3中级别的写锁优先级:
|
||||
1、高级:在获取写锁失败时设置写锁保护结束时间;在获取写锁成功时重置。
|
||||
2、中级:在获取写锁失败时设置写锁保护结束时间;在释放锁时重置。
|
||||
3、无:不设置写锁保护时间。
|
||||
*/
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 读写锁对象
|
||||
type RWLocker struct {
|
||||
read int
|
||||
write int // 使用int而不是bool值的原因,是为了与read保持类型的一致;
|
||||
writeProtectEndTime int64 // 写锁保护结束时间。如果当前时间小于该值,则会阻塞读锁请求;以便于提高写锁的优先级,避免连续的读锁导致写锁无法获得;
|
||||
prevStack []byte
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// 尝试加写锁
|
||||
// 返回值:加写锁是否成功
|
||||
func (this *RWLocker) lock() bool {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
|
||||
// 如果已经被锁定,则返回失败;并且设置写锁保护结束时间;以便于写锁可以优先竞争锁;
|
||||
if this.write == 1 || this.read > 0 {
|
||||
this.writeProtectEndTime = time.Now().UnixNano() + con_Write_Protect_Nanoseconds
|
||||
return false
|
||||
}
|
||||
|
||||
// 否则,将写锁数量设置为1,并返回成功;并重置写锁保护结束时间;这样读锁和写锁都可以参与锁的竞争;
|
||||
this.write = 1
|
||||
this.writeProtectEndTime = time.Now().UnixNano()
|
||||
|
||||
// 记录Stack信息
|
||||
if if_record_stack_info {
|
||||
this.prevStack = debug.Stack()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 写锁定
|
||||
// timeout:超时毫秒数,timeout<=0则将会死等
|
||||
// 返回值:
|
||||
// 成功或失败
|
||||
// 如果失败,返回上一次成功加锁时的堆栈信息
|
||||
// 如果失败,返回当前的堆栈信息
|
||||
func (this *RWLocker) Lock(timeout int) (successful bool, prevStack string, currStack string) {
|
||||
timeout = getTimeout(timeout)
|
||||
|
||||
// 遍历指定的次数(即指定的超时时间)
|
||||
for i := 0; i < timeout; i++ {
|
||||
// 如果锁定成功,则返回成功
|
||||
if this.lock() {
|
||||
successful = true
|
||||
break
|
||||
}
|
||||
|
||||
// 如果锁定失败,则休眠1ms,然后再重试
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
// 如果时间结束仍然是失败,则返回上次成功的堆栈信息,以及当前的堆栈信息
|
||||
if successful == false {
|
||||
if this.prevStack != nil && len(this.prevStack) > 0 {
|
||||
prevStack = string(this.prevStack)
|
||||
}
|
||||
currStack = string(debug.Stack())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 写锁定(死等)
|
||||
func (this *RWLocker) WaitLock() {
|
||||
successful, prevStack, currStack := this.Lock(0)
|
||||
if successful == false {
|
||||
fmt.Printf("RWLocker:WaitLock():{PrevStack:%s, currStack:%s}\n", prevStack, currStack)
|
||||
}
|
||||
}
|
||||
|
||||
// 释放写锁
|
||||
func (this *RWLocker) Unlock() {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
this.write = 0
|
||||
//释放写锁保护时间,避免调时间导致读锁超时
|
||||
this.writeProtectEndTime = 0
|
||||
}
|
||||
|
||||
// 尝试加读锁
|
||||
// 返回值:加读锁是否成功
|
||||
func (this *RWLocker) rlock() bool {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
|
||||
// 如果已经被锁定,或者处于写锁保护时间段内,则返回失败
|
||||
if this.write == 1 || time.Now().UnixNano() < this.writeProtectEndTime {
|
||||
return false
|
||||
}
|
||||
|
||||
// 否则,将读锁数量加1,并返回成功
|
||||
this.read += 1
|
||||
|
||||
// 记录Stack信息
|
||||
if if_record_stack_info {
|
||||
this.prevStack = debug.Stack()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 读锁定
|
||||
// timeout:超时毫秒数,timeout<=0则将会死等
|
||||
// 返回值:
|
||||
// 成功或失败
|
||||
// 如果失败,返回上一次成功加锁时的堆栈信息
|
||||
// 如果失败,返回当前的堆栈信息
|
||||
func (this *RWLocker) RLock(timeout int) (successful bool, prevStack string, currStack string) {
|
||||
timeout = getTimeout(timeout)
|
||||
|
||||
// 遍历指定的次数(即指定的超时时间)
|
||||
// 读锁比写锁优先级更低,所以每次休眠2ms,所以尝试的次数就是时间/2
|
||||
for i := 0; i < timeout; i++ {
|
||||
// 如果锁定成功,则返回成功
|
||||
if this.rlock() {
|
||||
successful = true
|
||||
break
|
||||
}
|
||||
|
||||
// 如果锁定失败,则休眠1ms,然后再重试
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
// 如果时间结束仍然是失败,则返回上次成功的堆栈信息,以及当前的堆栈信息
|
||||
if successful == false {
|
||||
if this.prevStack != nil && len(this.prevStack) > 0 {
|
||||
prevStack = string(this.prevStack)
|
||||
}
|
||||
currStack = string(debug.Stack())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 读锁定(死等)
|
||||
func (this *RWLocker) WaitRLock() {
|
||||
successful, prevStack, currStack := this.RLock(0)
|
||||
if successful == false {
|
||||
fmt.Printf("RWLocker:WaitRLock():{PrevStack:%s, currStack:%s}\n", prevStack, currStack)
|
||||
}
|
||||
}
|
||||
|
||||
// 释放读锁
|
||||
func (this *RWLocker) RUnlock() {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
if this.read > 0 {
|
||||
this.read -= 1
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新的读写锁对象
|
||||
func NewRWLocker() *RWLocker {
|
||||
return &RWLocker{}
|
||||
}
|
||||
58
trunk/goutil/syncUtil/rwLockerUtil.go
Normal file
58
trunk/goutil/syncUtil/rwLockerUtil.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 读写锁工具类
|
||||
type RWLockerUtil struct {
|
||||
// 锁集合
|
||||
lockerMap map[string]*RWLocker
|
||||
|
||||
// 锁对象
|
||||
rwMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// 创建新的锁工具类
|
||||
func NewRWLockerUtil() *RWLockerUtil {
|
||||
return &RWLockerUtil{
|
||||
lockerMap: make(map[string]*RWLocker, 8),
|
||||
}
|
||||
}
|
||||
|
||||
// 获取锁对象
|
||||
// lockName:锁名
|
||||
// 返回值:
|
||||
// RWLocker:读写锁对象
|
||||
func (this *RWLockerUtil) GetLock(lockName string) *RWLocker {
|
||||
var rwLockerObj *RWLocker
|
||||
var exists bool
|
||||
|
||||
func() {
|
||||
this.rwMutex.RLock()
|
||||
defer this.rwMutex.RUnlock()
|
||||
rwLockerObj, exists = this.lockerMap[lockName]
|
||||
}()
|
||||
if exists {
|
||||
return rwLockerObj
|
||||
}
|
||||
|
||||
this.rwMutex.Lock()
|
||||
defer this.rwMutex.Unlock()
|
||||
|
||||
rwLockerObj, exists = this.lockerMap[lockName]
|
||||
if exists == false {
|
||||
rwLockerObj = NewRWLocker()
|
||||
this.lockerMap[lockName] = rwLockerObj
|
||||
}
|
||||
|
||||
return rwLockerObj
|
||||
}
|
||||
|
||||
// 释放读写锁对象
|
||||
// lockName:锁名
|
||||
func (this *RWLockerUtil) ReleaseLock(lockName string) {
|
||||
this.rwMutex.Lock()
|
||||
defer this.rwMutex.Unlock()
|
||||
delete(this.lockerMap, lockName)
|
||||
}
|
||||
27
trunk/goutil/syncUtil/rwLockerUtil_test.go
Normal file
27
trunk/goutil/syncUtil/rwLockerUtil_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
rwLockerUtilObj = NewRWLockerUtil()
|
||||
)
|
||||
|
||||
func RWLockerUtil_TestGetLock(t *testing.T) {
|
||||
count := 100
|
||||
for i := 1; i <= count; i++ {
|
||||
rwLockerUtilObj.GetLock(fmt.Sprintf("%d", i))
|
||||
if lockerCount := len(rwLockerUtilObj.lockerMap); lockerCount != i {
|
||||
t.Errorf("(GetLock)Expected %d locker, but now got: %d", count, lockerCount)
|
||||
}
|
||||
}
|
||||
|
||||
for i := count; i > 0; i-- {
|
||||
rwLockerUtilObj.ReleaseLock(fmt.Sprintf("%d", i))
|
||||
if lockerCount := len(rwLockerUtilObj.lockerMap); lockerCount != i-1 {
|
||||
t.Errorf("(ReleaseLock)Expected %d locker, but now got: %d", count, lockerCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
154
trunk/goutil/syncUtil/rwLocker_test.go
Normal file
154
trunk/goutil/syncUtil/rwLocker_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package syncUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRWNewLocker1(t *testing.T) {
|
||||
count := 1000000
|
||||
succeedCount := 0
|
||||
expected := 1000000
|
||||
goroutineCount := 1
|
||||
|
||||
lockerObj := NewRWLocker()
|
||||
ch := make(chan bool, goroutineCount)
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
go rwLockerTest(lockerObj, &succeedCount, count/goroutineCount, ch)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
<-ch
|
||||
}
|
||||
|
||||
if succeedCount != expected {
|
||||
t.Errorf("Expected %d, but got %d", expected, succeedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRWNewLocker2(t *testing.T) {
|
||||
count := 1000000
|
||||
succeedCount := 0
|
||||
expected := 1000000
|
||||
goroutineCount := 100
|
||||
|
||||
lockerObj := NewRWLocker()
|
||||
ch := make(chan bool, goroutineCount)
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
go rwLockerTest(lockerObj, &succeedCount, count/goroutineCount, ch)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
<-ch
|
||||
}
|
||||
|
||||
if succeedCount != expected {
|
||||
t.Errorf("Expected %d, but got %d", expected, succeedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRWNewLocker3(t *testing.T) {
|
||||
count := 1000000
|
||||
succeedCount := 0
|
||||
expected := 1000000
|
||||
goroutineCount := 10000
|
||||
|
||||
lockerObj := NewRWLocker()
|
||||
ch := make(chan bool, goroutineCount)
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
go rwLockerTest(lockerObj, &succeedCount, count/goroutineCount, ch)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutineCount; i++ {
|
||||
<-ch
|
||||
}
|
||||
|
||||
if succeedCount != expected {
|
||||
t.Errorf("Expected %d, but got %d", expected, succeedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRWNewLocker4(t *testing.T) {
|
||||
lockerObj := NewRWLocker()
|
||||
if successful, _, _ := lockerObj.RLock(100); successful == false {
|
||||
t.Errorf("It should be successful to get a read lock, but now it fails.")
|
||||
return
|
||||
}
|
||||
if successful, _, _ := lockerObj.RLock(100); successful == false {
|
||||
t.Errorf("It should be successful to get a read lock, but now it fails.")
|
||||
return
|
||||
}
|
||||
if successful, _, _ := lockerObj.RLock(100); successful == false {
|
||||
t.Errorf("It should be successful to get a read lock, but now it fails.")
|
||||
return
|
||||
}
|
||||
lockerObj.RUnlock()
|
||||
lockerObj.RUnlock()
|
||||
lockerObj.RUnlock()
|
||||
|
||||
if successful, _, _ := lockerObj.Lock(100); successful == false {
|
||||
t.Errorf("It should be successful to get a write lock, but now it fails.")
|
||||
return
|
||||
}
|
||||
if successful, _, _ := lockerObj.Lock(100); successful {
|
||||
t.Errorf("It should be failed to get a write lock, but now it succeeds.")
|
||||
return
|
||||
}
|
||||
if successful, _, _ := lockerObj.RLock(100); successful {
|
||||
t.Errorf("It should be failed to get a read lock, but now it succeeds.")
|
||||
return
|
||||
}
|
||||
|
||||
lockerObj.Unlock()
|
||||
}
|
||||
|
||||
func TestRWNewLocker5(t *testing.T) {
|
||||
count := 100
|
||||
rwLockerObj := NewRWLocker()
|
||||
ch := make(chan bool, 100)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
go func(num int, ch chan bool) {
|
||||
if num%2 == 0 {
|
||||
if successful, _, _ := rwLockerObj.Lock(100); successful {
|
||||
fmt.Println("I get write lock.")
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
rwLockerObj.Unlock()
|
||||
} else {
|
||||
fmt.Println("Write lock timeout")
|
||||
}
|
||||
} else {
|
||||
if successful, _, _ := rwLockerObj.RLock(100); successful {
|
||||
fmt.Println("I get read lock.")
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
rwLockerObj.RUnlock()
|
||||
} else {
|
||||
fmt.Println("Read lock timeout")
|
||||
}
|
||||
}
|
||||
ch <- true
|
||||
}(i, ch)
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
<-ch
|
||||
}
|
||||
}
|
||||
|
||||
func rwLockerTest(lockerObj *RWLocker, succeedCount *int, count int, ch chan bool) {
|
||||
if success, _, _ := lockerObj.Lock(10000); !success {
|
||||
fmt.Printf("[%v]获取锁超时\n", time.Now())
|
||||
return
|
||||
}
|
||||
defer lockerObj.Unlock()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
*succeedCount += 1
|
||||
}
|
||||
|
||||
ch <- true
|
||||
}
|
||||
33
trunk/goutil/syncUtil/syncUtil.go
Normal file
33
trunk/goutil/syncUtil/syncUtil.go
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
由于Go不提供超时锁,所以自己实现了支持超时机制的互斥锁Locker和读写锁RWLocker。
|
||||
为了方便供第三方程序使用,提供了根据Key获取超时互斥锁和超时读写锁的复合对象LockerUtil和RWLockerUtil。
|
||||
为了在出现锁超时时方便查找问题,会记录上次成功获得锁时的堆栈信息;并且在本次获取锁失败时,同时返回上次成功时的堆栈信息和本次的堆栈信息。
|
||||
*/
|
||||
package syncUtil
|
||||
|
||||
const (
|
||||
// 默认超时的毫秒数(1小时)
|
||||
con_Default_Timeout_Milliseconds = 60 * 60 * 1000
|
||||
|
||||
// 写锁保护时间(纳秒)
|
||||
con_Write_Protect_Nanoseconds = 5 * 1000 * 1000
|
||||
)
|
||||
|
||||
var (
|
||||
// 是否记录堆栈信息的状态
|
||||
if_record_stack_info = false
|
||||
)
|
||||
|
||||
// 获取超时时间
|
||||
func getTimeout(timeout int) int {
|
||||
if timeout > 0 {
|
||||
return timeout
|
||||
} else {
|
||||
return con_Default_Timeout_Milliseconds
|
||||
}
|
||||
}
|
||||
|
||||
// 设置是否记录堆栈信息的状态
|
||||
func SetIfRecordStackInfo(value bool) {
|
||||
if_record_stack_info = value
|
||||
}
|
||||
Reference in New Issue
Block a user