自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go Commons Pool發(fā)布以及Golang多線程編程問題總結(jié)

開發(fā) 開發(fā)工具
Apache Commons Pool的核心是基于LinkedBlockingDeque,idle對(duì)象都放在deque中。

趁著元旦放假,整理了一下最近學(xué)習(xí)Golang時(shí),『翻譯』的一個(gè)Golang的通用對(duì)象池,放到 github (https://github.com/jolestar/go-commons-pool) 開源出來。之所以叫做『翻譯』,是因?yàn)檫@個(gè)庫的核心算法以及邏輯都是基于 Apache Commons Pool 的,只是把原來的Java『翻譯』成了Golang。

前一段時(shí)間閱讀kubernetes源碼的時(shí)候,整體上學(xué)習(xí)了下Golang,但語言這種東西,學(xué)了不用,幾個(gè)星期就忘差不多了。一次Golang實(shí)踐群里聊天,有人問到Golang是否有通用的對(duì)象池,搜索了下,貌似沒有比較完備的。當(dāng)前Golang的pool有以下解決方案:

1.sync.Pool

sync.Pool 使用很簡(jiǎn)單,只需要傳遞一個(gè)創(chuàng)建對(duì)象的func即可。

  1. var objPool = sync.Pool{ 
  2. New: func() interface{} { 
  3. return NewObject()}} 
  4. p := objPool.Get().(*Object) 

但sync.Pool只解決對(duì)象復(fù)用的問題,pool中的對(duì)象生命周期是兩次gc之間,gc后pool中的對(duì)象會(huì)被回收,使用方不能控制對(duì)象的生命周期,所以不適合用在連接池等場(chǎng)景。

2.通過container/list來實(shí)現(xiàn)自定義的pool,比如redigo 就使用這種辦法。但這些自定義的pool大多都不是通用的,功能也不完備。比如redigo當(dāng)前沒有獲取連接池的超時(shí)機(jī)制,參看這個(gè)issue Blocking with timeout when Get PooledConn

而Java中的commons pool,功能比較完備,算法和邏輯也經(jīng)過驗(yàn)證,使用也比較廣泛,所以就直接『翻譯』過來,順便練習(xí)Golang的語法。

作為一個(gè)通用的對(duì)象池,需要包含以下主要功能:

  1. 對(duì)象的生命周期可以精確控制 Pool提供機(jī)制允許使用方自定義對(duì)象的創(chuàng)建/銷毀/校驗(yàn)邏輯
  2. 對(duì)象的存活數(shù)量可以精確控制 Pool提供設(shè)置存活數(shù)量以及時(shí)長(zhǎng)的配置
  3. 獲取對(duì)象有超時(shí)機(jī)制避免死鎖,方便使用方實(shí)現(xiàn)failover 以前也遇到過許多線上故障,就是因?yàn)檫B接池的設(shè)置或者實(shí)現(xiàn)機(jī)制有缺陷導(dǎo)致的。

Apache Commons Pool的核心是基于LinkedBlockingDeque,idle對(duì)象都放在deque中。之所以是deque,而不是queue,是因?yàn)樗С諰IFO(last in, first out) /FIFO(first in, first out) 兩種策略獲取對(duì)象。然后有個(gè)包含所有對(duì)象的Map,key是用戶自定義對(duì)象,value是PooledObject,用于校驗(yàn)Return Object的合法性,后臺(tái)定時(shí)abandoned時(shí)遍歷,計(jì)算活躍對(duì)象數(shù)等。超時(shí)是通過Java鎖的wait timeout機(jī)制實(shí)現(xiàn)的。

下面總結(jié)下將Java翻譯成Golang的時(shí)候遇到的多線程問題

遞歸鎖或者叫可重入鎖(Recursive Lock)

Java中的synchronized關(guān)鍵詞以及LinkedBlockingDequeu中用到的ReentrantLock,都是可重入的。而Golang中的sync.Mutex是不可重入的。表現(xiàn)出來就是:

  1. ReentrantLock lock; 
  2.  
  3. public void a(){ 
  4.     lock.lock(); 
  5.     //do some thing 
  6.     lock.unlock(); 
  7.  
  8. public void b(){ 
  9.     lock.lock(); 
  10.     //do some thing 
  11.     lock.unlock(); 
  12.  
  13. public void all(){ 
  14.     lock.lock(); 
  15.     //do some thing 
  16.     a(); 
  17.     //do some thing 
  18.     b(); 
  19.     //do some thing 
  20.     lock.unlock(); 

上例all方法中嵌套調(diào)用a方法,雖然調(diào)用a方法的時(shí)候也需要鎖,但因?yàn)閍ll已經(jīng)申請(qǐng)鎖,并且該鎖可重入,所以不會(huì)導(dǎo)致死鎖。而同樣的代碼在Golang中是會(huì)導(dǎo)致死鎖的:

  1. var lock sync.Mutex 
  2.  
  3. func a() { 
  4.     lock.Lock() 
  5.     //do some thing 
  6.     lock.Unlock() 
  7.  
  8. func b() { 
  9.     lock.Lock() 
  10.     //do some thing 
  11.     lock.Unlock() 
  12.  
  13. func all() { 
  14.     lock.Lock() 
  15.     //do some thing 
  16.     a() 
  17.     //do some thing 
  18.     b() 
  19.     //do some thing 
  20.     lock.Unlock() 

只能重構(gòu)為下面這樣的(命名不規(guī)范請(qǐng)忽略,只是demo)

  1. var lock sync.Mutex 
  2.  
  3. func a() { 
  4.     lock.Lock() 
  5.     a1() 
  6.     lock.Unlock() 
  7.  
  8. func a1() { 
  9.     //do some thing 
  10.  
  11. func b() { 
  12.     lock.Lock() 
  13.     b1() 
  14.     lock.Unlock() 
  15.  
  16. func b1() { 
  17.     //do some thing 
  18.  
  19. func all() { 
  20.     lock.Lock() 
  21.     //do some thing 
  22.     a1() 
  23.     //do some thing 
  24.     b1() 
  25.     //do some thing 
  26.     lock.Unlock() 

Golang的核心開發(fā)者認(rèn)為可重入鎖是不好的設(shè)計(jì),所以不提供,參看Recursive (aka reentrant) mutexes are a bad idea。于是我們使用鎖的時(shí)候就需要多注意嵌套以及遞歸調(diào)用。

鎖等待超時(shí)機(jī)制

Golang的 sync.Cond 只有Wait,沒有如Java中的Condition的超時(shí)等待方法await(long time, TimeUnit unit)。這樣就沒法實(shí)現(xiàn)LinkBlockingDeque的 pollFirst(long timeout, TimeUnit unit) 這樣的方法。有人提了issue,但被拒絕了 sync: add WaitTimeout method to Cond。 所以只能通過channel的機(jī)制模擬了一個(gè)超時(shí)等待的Cond。完整源碼參看 go-commons-pool/concurrent/cond.go。

  1. type TimeoutCond struct { 
  2.     L      sync.Locker 
  3.     signal chan int 
  4.  
  5. func NewTimeoutCond(l sync.Locker) *TimeoutCond { 
  6.     cond := TimeoutCond{L: l, signal: make(chan int, 0)} 
  7.     return &cond 
  8.  
  9. /** 
  10. return remain wait timeand is interrupt 
  11. */ 
  12. func (this *TimeoutCond) WaitWithTimeout(timeout time.Duration) (time.Duration, bool) { 
  13.     //wait should unlock mutex,  if not will cause deadlock 
  14.     this.L.Unlock() 
  15.     defer this.L.Lock() 
  16.     begin := time.Now().Nanosecond() 
  17.     select { 
  18.     case _, ok := <-this.signal: 
  19.         end := time.Now().Nanosecond() 
  20.         return time.Duration(end - begin), !ok 
  21.     case <-time.After(timeout): 
  22.         return 0, false 
  23.     } 

Map機(jī)制的問題

這個(gè)問題嚴(yán)格的說不屬于多線程的問題。雖然Golang的map不是線程安全的,但通過mutex封裝一下也很容易實(shí)現(xiàn)。關(guān)鍵問題在于我們前面提到的,pool中用于維護(hù)全部對(duì)象的map,key是用戶自定義對(duì)象,value是PooledObject。而Golang對(duì)map的key的約束是:go-spec#Map_types

  • The comparison operators == and != must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice. If the key type is an interface type, these comparison operators must be defined for the dynamic key values; failure will cause a run-time panic.

也就是說key中不能包含不可比較的值,比如 slice, map, and function。而我們的key是用戶自定義的對(duì)象,沒辦法進(jìn)行約束。于是借鑒Java的IdentityHashMap的思路,將key轉(zhuǎn)換成對(duì)象的指針地址,實(shí)際上map中保存的是key對(duì)象的指針地址。

  1. type SyncIdentityMap struct { 
  2.     sync.RWMutex 
  3.     m map[uintptr]interface{} 
  4.  
  5. func (this *SyncIdentityMap) Get(key interface{}) interface{} { 
  6.     this.RLock() 
  7.     keyPtr := genKey(key
  8.     value := this.m[keyPtr] 
  9.     this.RUnlock() 
  10.     return value 
  11.  
  12. func genKey(key interface{}) uintptr { 
  13.     keyValue := reflect.ValueOf(key
  14.     return keyValue.Pointer() 

同時(shí),這樣做的缺點(diǎn)是Pool中存的對(duì)象必須是指針,不能是值對(duì)象。比如string,int等對(duì)象是不能保存到Pool中的。

其他的關(guān)于多線程的題外話

Golang的test -race 參數(shù)非常好用,通過這個(gè)參數(shù),發(fā)現(xiàn)了幾個(gè)data race的bug,參看commit fix data race test error。

Go Commons Pool后續(xù)工作

  1. 繼續(xù)完善測(cè)試用例,測(cè)試用例當(dāng)前已經(jīng)完成了大約一半多,覆蓋率88%?!悍g』的時(shí)候,主體代碼相對(duì)來說寫起來很快,但測(cè)試用例就比較麻煩多了,多線程情況下調(diào)試也比較復(fù)雜。一般基礎(chǔ)庫的測(cè)試用例代碼是核心邏輯代碼的2-3倍。
  2. 做下benchmark。核心算法上應(yīng)該沒啥問題,都是進(jìn)過驗(yàn)證的。但用channel模擬timeout的機(jī)制上可能有瓶頸。這塊要考慮timer的復(fù)用機(jī)制。參看 Terry-Mao/goim
  3. 上兩項(xiàng)完成了,就可以準(zhǔn)備發(fā)布個(gè)正式版本,可以通過這個(gè)pool改進(jìn)下redigo。

【本文為51CTO專欄作者“王淵命”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過作者微信公眾號(hào)jolestar-blog獲取授權(quán)】

戳這里,看該作者更多好文

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2012-05-18 10:36:20

CC++編程

2015-12-22 10:39:52

Java多線程問題

2017-01-19 10:24:38

Java多線程問題

2010-03-16 19:29:26

Java多線程操作

2021-03-05 07:38:52

C++線程編程開發(fā)技術(shù)

2010-01-28 09:55:05

性能優(yōu)化

2013-07-16 10:12:14

iOS多線程多線程概念多線程入門

2023-06-13 13:39:00

多線程異步編程

2009-03-12 10:52:43

Java線程多線程

2010-03-16 18:40:59

Java多線程編程

2023-04-02 17:53:10

多線程編程自測(cè)

2023-06-05 07:56:10

線程分配處理器

2023-06-06 08:17:52

多線程編程Thread類

2023-06-07 13:49:00

多線程編程C#

2025-01-23 08:33:27

2009-06-11 10:48:53

Java多線程

2009-06-11 10:22:18

Java多線程

2010-02-01 17:25:09

Python多線程

2013-07-16 10:57:34

iOS多線程多線程概念多線程入門

2011-06-13 10:41:17

JAVA
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)