Administrator
发布于 2023-06-25 / 56 阅读
0
0

Go Concurrency Two

When to use Mutexes instead of channels

首先,如果能用channel,尽量用channel;如果实在不能用channel,那么使用mutex。

为什么要这样说呢?

go community中,有这样一句话:

Share memory by communicating; do not communicate by sharing memory.

这句话是什么意思呢?
在Java并发模型中,多个线程之间如果想要communicate,那么就需要使用共享内存,并且为了线程安全,需要给共享内存加锁。线程之间,需要先竞争获取到锁后,才能访问 和 操作这个共享内存。

但是在Go中,Go encourages the use of channels to pass references to data between goroutines. This approach ensures that only one goroutine has access to the data at a given time.

因为channel中的数据,只能被一个goroutinue访问,其他的goroutinue无法再访问到同样的数据了。

也就是说,channel带来的最大作用是:能够自动保证,一个数据,只能被一个go routinue访问,其他的goroutinue无法访问这个同样的数据了。而共享内存本身,并不能保证这点,所有的线程都能访问同一个数据,为了保证数据不能篡改,我们又引入锁的概念,其实目的同样是保证同一个时刻,同一份数据,只能被一个线程使用。

That said, sometimes it is clearer to use a mutex and the Go standard library includes mutex implementations for these situations.

The most common case is when your goroutines read or write a shared value, but don’t process the value

这句话的意思是:当我们的goroutines只会读取数据,或者新增数据,而不是修改数据时,此时使用锁,更加好一点。

package main

import "fmt"

func main() {
	csm, closeFunc := NewChannelScoreboardManager()
	csm.update("zhangsan", 10)
	score1, ok := csm.Read("zhangsan")
	if ok {
		fmt.Println(score1)
	}


	csm.update("lisi", 15)
	score2, ok := csm.Read("lisi")
	if ok {
		fmt.Println(score2)
	}

	// 这个是将scoreboardManager 这个go routinue,结束掉,防止resource leak
	closeFunc()
}

type ChannelScoreboardManager chan func(map[string]int)

func NewChannelScoreboardManager() (ChannelScoreboardManager, func()) {
	ch := make(ChannelScoreboardManager)
	done := make(chan struct{})
	// 这里启动了一个go routinue
	go scoreboardManager(ch, done)
	return ch, func() {
		// 这里,提供了,结束这个go routinue的指令
		close(done)
	}

}

func scoreboardManager(in <-chan func(map[string]int), done <-chan struct{}) {
	// 后面传入的zhangsan  10等数据,就存储在这个map中
	// 这个map,会被传入到update 和 read中
	scoreboard := map[string]int{}

	// 这里使用for循环的目的,可以一直从,这个in的channel中取到函数,并执行函数,直到done被close了
	for {
		select {
		case <-done:
			return
		case f := <-in:
			f(scoreboard)
		}
	}
}

func (csm ChannelScoreboardManager) update(name string, val int) {
	csm <- func(m map[string]int) {
		m[name] = val
	}
}

func (csm ChannelScoreboardManager) Read(name string) (int, bool) {
	var out int
	var ok bool
	done := make(chan struct{})
	csm <- func(m map[string]int) {
		out, ok = m[name]
		close(done)
	}
	// 这里的从done channel中读取值,会处于block,
	// 直到从map中读取到值后,调用了close(done),这个read方法,才能返回,否则,返回的数据,是个nil
	<-done
	return out, ok
}

执行结果如下:

10
15

上面的代码,是使用channel,来实现的。我们发现,很繁琐,而且,一次只能执行一个操作。

下面,我们使用mutex,来实现这个

There are two mutex implementations in the standard library, both in the sync package. 

The first is called Mutex and has two methods, Lock and Unlock. 

Calling Lock causes the current goroutine to pause as long as another goroutine is currently in the critical section.

When the critical section is clear, the lock is acquired by the current goroutine and the code in the critical section is executed. 
A call to the Unlock method on the Mutex marks the end of the critical section. 

The second mutex implementation is called RWMutex and it allows you to have both reader locks and writer locks.

While only one writer can be in the critical section at a time, reader locks are shared; multiple readers can be in the critical section at once. 

The writer lock is managed with the Lock and Unlock methods, while the reader lock is managed with RLock and RUnlock methods.
Any time you acquire a mutex lock, you must make sure that you release the lock. 

Use a defer statement to call Unlock immediately after calling Lock or RLock.
package main

import (
	"fmt"
	"sync"
)

func main() {
	msm := NewMutexScoreboardManager()
	msm.Update("zhangsan",10)
	fmt.Println(msm.Read("zhangsan"))

	msm.Update("lisi",15)
	fmt.Println(msm.Read("lisi"))
}

type MutexScoreboardManager struct {
	l          sync.RWMutex
	scoreboard map[string]int
}

func NewMutexScoreboardManager() *MutexScoreboardManager {
	return &MutexScoreboardManager{
		scoreboard: map[string]int{},
	}
}

func (msm *MutexScoreboardManager) Update(name string, score int) {
	msm.l.Lock()
	defer msm.l.Unlock()
	msm.scoreboard[name] = score
}

func (msm *MutexScoreboardManager) Read(name string) (int, bool) {
	msm.l.RLock()
	defer msm.l.RUnlock()
	val, ok := msm.scoreboard[name]
	return val, ok
}

执行结果如下:

10 true
15 true

那么什么时候,使用channel?什么时候,使用mutex

If you are coordinating goroutines or tracking a value as it is transformed by a series of goroutines, use channels.

If you are sharing access to a field in a struct, use mutexes.

If you discover a critical performance issue when using channels (see “Benchmarks” to learn how to do this), and you cannot find any other way to fix the issue, modify your code to use a mutex.
Since our scoreboard is a field in a struct and there’s no transfer of the scoreboard, using a mutex makes sense. 

This is a good use for a mutex only because the data is stored in-memory.

When data is stored in external services, like an HTTP server or a database, don’t use a mutex to guard access to the system.
Mutexes require you to do more bookkeeping. For example, you must correctly pair locks and unlocks or your programs will likely deadlock.
Our example both acquires and releases the locks within the same method.


Another issue is that mutexes in Go aren’t reentrant. 
If a goroutine tries to acquire the same lock twice, it deadlocks, waiting for itself to release the lock. This is different from languages like Java, where locks are reentrant.
Non-reentrant locks make it tricky to acquire a lock in a function that calls itself recursively. 

You must release the lock before the recursive function call. In general, be careful when holding a lock while making a function call, because you don’t know what locks are going to be acquired in those calls.

If your function calls another function that tries to acquire the same mutex lock, the goroutine deadlocks
Never try to access a variable from multiple goroutines unless you acquire a mutex for that variable first. 

It can cause odd errors that are hard to trace. See “Finding Concurrency Problems with the Race Checker” to learn how to detect these problems.

评论