Go Commons Pool发布以及Golang多线程编程问题总结
转自: http://jolestar.com/go-commons-pool-and-go-concurrent/
Go Commons Pool发布以及Golang多线程编程问题总结趁着元旦放假,整理了一下最近学习Golang时,『翻译』的一个Golang的通用对象池,放到 githubGo Commons Pool开源出来。之所以叫做『翻译』,是因为这个库的核心算法以及逻辑都是基于Apache Commons Pool的,只是把原来的Java『翻译』成了Golang。 前一段时间阅读kubernetes源码的时候,整体上学习了下Golang,但语言这种东西,学了不用,几个星期就忘差不多了。一次Golang实践群里聊天,有人问到Golang是否有通用的对象池,搜索了下,貌似没有比较完备的。当前Golang的pool有以下解决方案:
而Java中的commons pool,功能比较完备,算法和逻辑也经过验证,使用也比较广泛,所以就直接『翻译』过来,顺便练习Golang的语法。 作为一个通用的对象池,需要包含以下主要功能:
Apache Commons Pool的核心是基于LinkedBlockingDeque,idle对象都放在deque中。之所以是deque,而不是queue,是因为它支持LIFO(last in,first out) /FIFO(first in,first out) 两种策略获取对象。然后有个包含所有对象的Map,key是用户自定义对象,value是PooledObject,用于校验Return Object的合法性,后台定时abandoned时遍历,计算活跃对象数等。超时是通过Java锁的wait timeout机制实现的。 下面总结下将Java翻译成Golang的时候遇到的多线程问题 递归锁或者叫可重入锁(Recursive Lock)Java中的synchronized关键词以及LinkedBlockingDequeu中用到的ReentrantLock,都是可重入的。而Golang中的sync.Mutex是不可重入的。表现出来就是: ReentrantLock lock;
public void a(){
lock.lock();
//do some thing
lock.unlock();
}
public void b(){
lock.lock();
//do some thing
lock.unlock();
}
public void all(){
lock.lock();
//do some thing
a();
//do some thing
b();
//do some thing
lock.unlock();
}
上例all方法中嵌套调用a方法,虽然调用a方法的时候也需要锁,但因为all已经申请锁,并且该锁可重入,所以不会导致死锁。而同样的代码在Golang中是会导致死锁的: var lock sync.Mutex
func a() {
lock.Lock()
//do some thing
lock.Unlock()
}
func b() {
lock.Lock()
//do some thing
lock.Unlock()
}
func all() {
lock.Lock()
//do some thing
a()
//do some thing
b()
//do some thing
lock.Unlock()
}
只能重构为下面这样的(命名不规范请忽略,只是demo) var lock sync.Mutex
func a() {
lock.Lock()
a1()
lock.Unlock()
}
func a1() {
//do some thing
}
func b() {
lock.Lock()
b1()
lock.Unlock()
}
func b1() {
//do some thing
}
func all() {
lock.Lock()
//do some thing
a1()
//do some thing
b1()
//do some thing
lock.Unlock()
}
Golang的核心开发者认为可重入锁是不好的设计,所以不提供,参看Recursive (aka reentrant) mutexes are a bad idea。于是我们使用锁的时候就需要多注意嵌套以及递归调用。 锁等待超时机制Golang的 sync.Cond 只有Wait,没有如Java中的Condition的超时等待方法await(long time,TimeUnit unit)。这样就没法实现LinkBlockingDeque的 pollFirst(long timeout,TimeUnit unit) 这样的方法。有人提了issue,但被拒绝了sync: add WaitTimeout method to Cond。 所以只能通过channel的机制模拟了一个超时等待的Cond。完整源码参看go-commons-pool/concurrent/cond.go。 type TimeoutCond struct {
L sync.Locker
signal chan int
}
func NewTimeoutCond(l sync.Locker) *TimeoutCond {
cond := TimeoutCond{L: l,signal: make(chan int,0)}
return &cond
}
/**
return remain wait time,and is interrupt
*/
func (this *TimeoutCond) WaitWithTimeout(timeout time.Duration) (time.Duration,bool) {
//wait should unlock mutex,if not will cause deadlock
this.L.Unlock()
defer this.L.Lock()
begin := time.Now().Nanosecond()
select {
case _,ok := <-this.signal:
end := time.Now().Nanosecond()
return time.Duration(end - begin),!ok
case <-time.After(timeout):
return 0,false
}
}
Map机制的问题这个问题严格的说不属于多线程的问题。虽然Golang的map不是线程安全的,但通过mutex封装一下也很容易实现。关键问题在于我们前面提到的,pool中用于维护全部对象的map,key是用户自定义对象,value是PooledObject。而Golang对map的key的约束是:go-spec#Map_types
也就是说key中不能包含不可比较的值,比如 slice,and function。而我们的key是用户自定义的对象,没办法进行约束。于是借鉴Java的IdentityHashMap的思路,将key转换成对象的指针地址,实际上map中保存的是key对象的指针地址。 type SyncIdentityMap struct {
sync.RWMutex
m map[uintptr]interface{}
}
func (this *SyncIdentityMap) Get(key interface{}) interface{} {
this.RLock()
keyPtr := genKey(key)
value := this.m[keyPtr]
this.RUnlock()
return value
}
func genKey(key interface{}) uintptr {
keyValue := reflect.ValueOf(key)
return keyValue.Pointer()
}
同时,这样做的缺点是Pool中存的对象必须是指针,不能是值对象。比如string,int等对象是不能保存到Pool中的。 其他的关于多线程的题外话Golang的test -race 参数非常好用,通过这个参数,发现了几个data race的bug,参看commit fix data race test error。 Go Commons Pool后续工作
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |