This is a creation in Article, where the information may have evolved or changed. In the go language, the use of channel communication is advocated to replace the explicit synchronization mechanism. But I have found that sometimes it does not seem very good to use the channel communication method (the problem of efficiency is not considered). Suppose you have a collection of accounts that you need to implement on this set, such as finding changes and so on. The operation of this collection must be concurrency-enabled.
If the lock is used (scenario 1)
The implementation is probably like this:
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}
Using channels to achieve the test (scenario 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}
Here's a question, every time you call find, you create a channel.
Then try to use the channel as a parameter (scenario 3)
Only the implementation of the find function needs to be modified:
// 信道对象作为参数,暴露了实现机制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}
To summarize, the problem now is that there are three kinds of solutions that are not satisfactory:
Scenario 1: Using the lock mechanism is not very consistent with the way go solves the problem.
Scenario 2: For queries that need to return results, each query creates a channel to compare waste resources.
Scenario 3: You need to specify the channel object in the function parameters, exposing the implementation mechanism.
So is there any better plan?
2012.12.14: Scenario 2 also has an improved version: Use of pre-allocation and recyclable channel to improve resource utilization. This technique is useful when multiple goroutine wait for an active object to return its own data. For example, the online game server in the login server each player's connection with a goroutine to deal with, another active object on behalf of the account server connection to verify the legitimacy of the account. Player Goroutine will send their respective player account password to the active object and block waiting for the active object to return the validation results. Because multiple players initiate an account verification request at the same time, the active object needs to distribute the returned results, so it can request a channel and wait for the channel when sending the request.
The code is as follows:
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}
Add a comment on Golang-china:
Xushiwei
In your way, use the channel to actually serialize all requests. In addition, the channel is far greater than the lock in terms of cost. Because the channel itself is obviously implemented with a lock + signal wakeup mechanism.
Steve Wang
Is it possible to summarize this: 1. For concurrent access to data objects that are shared with each goroutine, use a lock to control 2. For communication between Goroutine, use the channel
Longshanksmo
In terms of performance alone, this conclusion is a little hasty now. Concurrency and performance problems are complex, and different scenarios can produce the exact opposite conclusion. There are a number of factors to consider: first, different use conditions, lock granularity is different. In your case, the map operation, the lock granularity is very small. However, if there is some kind of overloaded operation, or there is a blockage, the lock granularity is large. It was not worth the lock at that time. Second, Chan's lock granularity is very small, basic fixed, predictable. In real business, performance predictability is important, determining the resource input and provisioning at deployment time. Most importantly, if all goroutine within a process are running within a single thread, then Chan's lock is not required. So as to really play the coroutine advantage. Now the go compiler doesn't seem to have optimized this yet, and I don't know if it will evolve in the future. In short, the concurrency has not yet been changed