[go语言]吐槽:怎么样实现支持并发访问的数据集合更好?

在go语言里,提倡用信道通讯的方式来替代显式的同步机制。但是我发现有的时候用信道通讯方式实现的似乎也不是很好(暂不考虑效率问题)。
假设有一个帐号的集合,需要在这个集合上实现一些操作,比如查找修改等。这个集合的操作必须是支持并发的。
如果用锁的方式(方案1)实现大概是这样:
import "sync"

type Info struct {

age int
}
type AccountMap struct {

accounts map[string]*Info
mutex 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 := *res


return &inf


}

用信道来实现试试(方案2):

type Info struct {


age int

}
type AccountMap struct {


accounts map[string]*Info


ch 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 := *res



c <- &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 := *res




c <- &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]*Info


ch 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.tokens

p.ch <- func() {



res, ok := p.accounts[name]


if !ok {




c <- nil



} else {




inf := *res



c <- &inf



}


}

inf := <-c
// 回收信道
p.tokens <- c
return inf

}

补充一下golang-china上的评论:


xushiwei
在你的方式里面,用 channel 其实把所有请求串行化。
另外,从成本上来说,channel 远大于锁。因为 channel 本身显然是用锁 + 信号唤醒机制实现的。

steve wang
是不是可以这样总结:
1.对于共享给各个goroutine的数据对象的并发访问,使用锁来控制
2.对于goroutine之间的通信,使用信道

longshanksmo

单就性能来看,现在下这种结论有些草率。并发和性能问题错宗复杂,不同的场景可能会产生完全相反的结论。
还有众多因素需要考虑:
首先,不同的用况下,锁粒度不同。在你的案例中是map操作,锁粒度很小。但如果是某种重载操作,或者存在阻塞,锁粒度会很大。那时用锁就不划算。
其次,chan的锁粒度很小,基本固定,可预测。在实际业务中,性能可预测非常重要,决定了部署时的资源投入和调配。
最重要一点,如果进程内的所有goroutine是在单个线程内运行,那么chan的锁是不需要的。这样才能真正发挥coroutine的优势。现在的go编译器似乎还没有对这个做优化,不知将来是否会进化。
总之,并发方面还没有一改而论

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-08-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏后端技术探索

网络安全之【XSS和XSRF攻击】

XSS又称CSS,全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。...

23630
来自专栏Java后端技术栈

【面试题】2018年最全Java面试通关秘籍第二套!

注:本文是从众多面试者的面试经验中整理而来,其中不少是本人出的一些题目,网络资源众多,如有雷同,纯属巧合!禁止一切形式的碰瓷行为!未经允许禁止一切形式的转载和复...

12710
来自专栏张善友的专栏

使用 ASP.NET Web API 构建超媒体 Web API

超媒体(通常称为应用程序状态的引擎 (HATEOAS))是具象状态传输 (REST) 的主要限制之一。有一种观念认为超媒体项目(如链接或表单)可用于说明客户端如...

30550
来自专栏Golang语言社区

论获取缓存值的正确姿势

论获取缓存值的正确姿势 cache 时至今日,大家对缓存想必不在陌生。我们身边各种系统中或多或少的都存在缓存,自从有个缓存,我们可以减少很多计算压力,提高应用程...

39580
来自专栏游戏杂谈

关于seajs

虽然已经有很长时间没写JavaScript,但很多时候看到一些应用还是会带着好奇心去研究一下。之前是看腾讯的朋友网,它的webchat做的很不错(虽然ff下有b...

40630
来自专栏高性能服务器开发

(六)关于网络编程的一些实用技巧和细节

这些年,接触了形形色色的项目,写了不少网络编程的代码,从windows到linux,跌进了不少坑,由于网络编程涉及很多细节和技巧,一直想写篇文章来总结下这方面的...

41570
来自专栏Java 源码分析

Netty 入门

1. 粘包问题 一 .长连接与短连接: 1.长连接:Client方与Server方先建立通讯连接,连接建立后不断开, 然后再进行报文发送和接收。长连接在 net...

31950
来自专栏逍遥剑客的游戏开发

Nebula3中的Entity

15250
来自专栏互联网技术栈

Dubbo作者聊 设计原则

转于自己在公司的Blog: http://pt.alibaba-inc.com/wp/experience_1301/code-detail.html

36340
来自专栏高性能服务器开发

(六)关于网络编程的一些实用技巧和细节

这些年,接触了形形色色的项目,写了不少网络编程的代码,从windows到linux,跌进了不少坑,由于网络编程涉及很多细节和技巧,一直想写篇文章来总结下这方面的...

47550

扫码关注云+社区

领取腾讯云代金券