[go語言]吐槽:怎麼樣實現支援並發訪問的資料集合更好?

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。在go語言裡,提倡用通道通訊的方式來替代顯式的同步機制。但是我發現有的時候用通道通訊方式實現的似乎也不是很好(暫不考慮效率問題)。假設有一個帳號的集合,需要在這個集合上實現一些操作,比如尋找修改等。這個集合的操作必須是支援並發的。

如果用鎖的方式(方案1)

實現大概是這樣:
import "sync"type Info struct {age int}type AccountMap struct {accounts map[string]*Infomutex    sync.Mutex}func NewAccountMap() *AccountMap {return &AccountMap{accounts: make(map[string]*Info),}}func (p *AccountMap) add(name string, age int) {p.mutex.Lock()defer p.mutex.Unlock()p.accounts[name] = &Info{age}}func (p *AccountMap) del(name string) {p.mutex.Lock()defer p.mutex.Unlock()delete(p.accounts, name)}func (p *AccountMap) find(name string) *Info {p.mutex.Lock()defer p.mutex.Unlock()res, ok := p.accounts[name]if !ok {return nil}inf := *resreturn &inf}

用通道來實現試試(方案2)

type Info struct {age int}type AccountMap struct {accounts map[string]*Infoch       chan func()}func NewAccountMap() *AccountMap {p := &AccountMap{accounts: make(map[string]*Info),ch:       make(chan func()),}go func() {for {(<-p.ch)()}}()return p}func (p *AccountMap) add(name string, age int) {p.ch <- func() {p.accounts[name] = &Info{age}}}func (p *AccountMap) del(name string) {p.ch <- func() {delete(p.accounts, name)}}func (p *AccountMap) find(name string) *Info {// 每次查詢都要建立一個通道c := make(chan *Info)p.ch <- func() {res, ok := p.accounts[name]if !ok {c <- nil} else {inf := *resc <- &inf}}return <-c}

這裡有個問題,每次調用find都要建立一個通道。

那麼試試把通道作為參數(方案3)

只需要修改find函數的實現:
// 通道對象作為參數,暴露了實現機制func (p *AccountMap) find(name string, c chan *Info) *Info {p.ch <- func() {res, ok := p.accounts[name]if !ok {c <- nil} else {inf := *resc <- &inf}}return <-c}

總結一下,現在的問題就是三種方案都有不盡如人意之處:

方案1:使用鎖機制,不太符合go解決問題的方式。

方案2:對於需要返回結果的查詢,每次查詢都要建立一個通道,比較浪費資源。

方案3:需要在函數參數中指定通道對象,把實現機制暴露了。

那麼有沒有什麼更好的方案呢?

2012.12.14:方案2 還有一個改進版本:利用預分配以及可回收的channel來提高資源使用率。這個技術在多個goroutine等待一個主動對象返回自己的資料時會比較有用。例如網遊伺服器中登入伺服器裡每個玩家的串連用一個goroutine來處理;另外一個主動對象代表帳號伺服器串連用於驗證帳號合法性。玩家goroutine會把各自的輸入的玩家帳號密碼發送給這個主動對象,並阻塞等待主動對象返回驗證結果。因為有多個玩家同時發起帳號驗證請求,所以主動對象需要把返回結果進行分發,因此可以在發送請求的時候申請一個通道並等待這個通道。

代碼如下:
type Info struct {age int}type AccountMap struct {accounts map[string]*Infoch       chan func()tokens   chan chan *Info}func NewAccountMap() *AccountMap {p := &AccountMap{accounts: make(map[string]*Info),ch:       make(chan func()),tokens:   make(chan chan *Info, 128),}for i := 0; i < cap(p.tokens); i++ {p.tokens <- make(chan *Info)}go func() {for {(<-p.ch)()}}()return p}func (p *AccountMap) add(name string, age int) {p.ch <- func() {p.accounts[name] = &Info{age}}}func (p *AccountMap) del(name string) {p.ch <- func() {delete(p.accounts, name)}}func (p *AccountMap) find(name string) *Info {// 每次查詢都要擷取一個通道c := <-p.tokensp.ch <- func() {res, ok := p.accounts[name]if !ok {c <- nil} else {inf := *resc <- &inf}}inf := <-c// 回收通道p.tokens <- creturn inf}

補充一下golang-china上的評論:

xushiwei

在你的方式裡面,用 channel 其實把所有請求序列化。另外,從成本上來說,channel 遠大於鎖。因為 channel 本身顯然是用鎖 + 訊號喚醒機制實現的。

steve wang

是不是可以這樣總結:1.對於共用給各個goroutine的資料對象的並發訪問,使用鎖來控制2.對於goroutine之間的通訊,使用通道

longshanksmo

單就效能來看,現在下這種結論有些草率。並發和效能問題錯宗複雜,不同的情境可能會產生完全相反的結論。還有眾多因素需要考慮:首先,不同的用況下,鎖粒度不同。在你的案例中是map操作,鎖粒度很小。但如果是某種重載操作,或者存在阻塞,鎖粒度會很大。那時用鎖就不划算。其次,chan的鎖粒度很小,基本固定,可預測。在實際業務中,效能可預測非常重要,決定了部署時的資源投入和調配。最重要一點,如果進程內的所有goroutine是在單個線程內運行,那麼chan的鎖是不需要的。這樣才能真正發揮coroutine的優勢。現在的go編譯器似乎還沒有對這個做最佳化,不知將來是否會進化。總之,並發方面還沒有一改而論

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.