如果用锁的方式(方案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)
// 信道对象作为参数,暴露了实现机制 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),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,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编译器似乎还没有对这个做优化,不知将来是否会进化。 总之,并发方面还没有一改而论
原文链接:https://www.f2er.com/go/190519.html