理解Go协程与并发(3)

如果把注释的部分打开,那么程序在打印出来自ch1、ch2的数据后,就会一直执行default里面的程序。这个时候程序不会退出。原因是当 select 语句所有通道都不可读写时,如果定义了 default 分支,那就会执行 default 分支逻辑。

注:select{}代码块是一个没有任何case的select,它会一直阻塞。

Chan的应用场景

golang中chan的应用场景总结
https://github.com/nange/blog/issues/9

Go语言之Channels实际应用
https://www.s0nnet.com/archives/go-channels-practice

消息队列

并发请求

模拟锁的功能

模拟sync.WaitGroup

并行计算

通道原理部分可以根据文末给出的参考链接《快学 Go 语言》第 12 课 —— 通道去查看。

并发锁 互斥所

go语言里的map是线程不安全的:

package main import "fmt" func write(d map[string]string) { d["name"] = "yujc" } func read(d map[string]string) { fmt.Println(d["name"]) } func main() { d := map[string]string{} go read(d) write(d) }

Go 语言内置了数据结构竞态检查工具来帮我们检查程序中是否存在线程不安全的代码,只要在运行的时候加上-race参数即可:

$ go run -race main.go ================== WARNING: DATA RACE Read at 0x00c0000a8180 by goroutine 6: ... yujc Found 2 data race(s) exit status 66

可以看出,上面的代码存在安全隐患。

我们可以使用sync.Mutex来保护map,原理是在每次读写操作之前使用互斥锁进行保护,防止其他线程同时操作:

package main import ( "fmt" "sync" ) type SafeDict struct { data map[string]string mux *sync.Mutex } func NewSafeDict(data map[string]string) *SafeDict { return &SafeDict{ data: data, mux: &sync.Mutex{}, } } func (d *SafeDict) Get(key string) string { d.mux.Lock() defer d.mux.Unlock() return d.data[key] } func (d *SafeDict) Set(key string, value string) { d.mux.Lock() defer d.mux.Unlock() d.data[key] = value } func main(){ dict := NewSafeDict(map[string]string{}) go func(dict *SafeDict) { fmt.Println(dict.Get("name")) }(dict) dict.Set("name", "yujc") }

运行检测:

$ go run -race main.go yujc

上面的代码如果不使用-race运行,不一定会有结果,取决于主协程、子协程哪个先运行。

注意:sync.Mutex 是一个结构体对象,这个对象在使用的过程中要避免被浅拷贝,否则起不到保护作用。应尽量使用它的指针类型。

上面的代码里我们多处使用了d.mux.Lock(),能否简化成d.Lock()呢?答案是可以的。我们知道,结构体可以自动继承匿名内部结构体的所有方法:

type SafeDict struct { data map[string]string *sync.Mutex } func NewSafeDict(data map[string]string) *SafeDict { return &SafeDict{data, &sync.Mutex{}} } func (d *SafeDict) Get(key string) string { d.Lock() defer d.Unlock() return d.data[key] }

这样就完成了简化。

读写锁

对于读多写少的场景,可以使用读写锁代替互斥锁,可以提高性能。

读写锁提供了下面4个方法:

Lock() 写加锁

Unlock() 写释放锁

RLock() 读加锁

RUnlock() 读释放锁

写锁是排它锁,加写锁时会阻塞其它协程再加读锁和写锁;读锁是共享锁,加读锁还可以允许其它协程再加读锁,但是会阻塞加写锁。读写锁在写并发高的情况下性能退化为普通的互斥锁。

我们把上节中的互斥锁换成读写锁:

package main import ( "fmt" "sync" ) type SafeDict struct { data map[string]string *sync.RWMutex } func NewSafeDict(data map[string]string) *SafeDict { return &SafeDict{data, &sync.RWMutex{}} } func (d *SafeDict) Get(key string) string { d.RLock() defer d.RUnlock() return d.data[key] } func (d *SafeDict) Set(key string, value string) { d.Lock() defer d.Unlock() d.data[key] = value } func main(){ dict := NewSafeDict(map[string]string{}) go func(dict *SafeDict) { fmt.Println(dict.Get("name")) }(dict) dict.Set("name", "yujc") }

改完后,使用竞态检测工具检测还是能通过的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/95a3a43bc0fa21cbdc737b160cb22deb.html