TODO
Datawhale组队学习
1. 并发与并行
Erlang 之父 Joe Armstrong曾经以下图解释并发与并行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vysv5ZD1-1608997001518)(./img/cor.jpg)]
并发在图中的解释是两队人排队接咖啡,两队切换。
并行是两个咖啡机,两队人同时接咖啡。
“Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.” — Rob Pike
并发使并行变得容易,并发提供了一种构造解决方案的方法,并行一般伴随这多核。并发一般伴随这CPU切换轮训。
2. 为什么需要并发?
原因有很多,其中比较重要的原因如下:
- 不阻塞等待其他任务的执行,从而浪费时间,影响系统性能。
- 并行可以使系统变得简单些,将复杂的大任务切换成许多小任务执行,单独测试。
在开发中,经常会遇到为什么某些进程通常会相互等待呢?为什么有些运行慢,有些快呢?
通常受限来源于进程I/O或CPU。
- 进程I/O限制
如:等待网络或磁盘访问
- CPU限制
如:大量计算
3. Go并发原语
3.1 协程Goroutines
每个go程序至少都有一个Goroutine:主Goroutine(在运行进程时自动创建)。以及程序中其他Goroutine
例如:下面程序创建了main的Goroutine及匿名的Goroutine。
func main() {
go func() {
fmt.Println("you forgot me !")
}()
}
在go中有个package是sync,里面包含了:
WaitGroup、Mutex、Cond、Once、Pool,下面依次介绍。
1.WaitGroup
假设主线程要等待其余的goroutine都运行完毕,不得不在末尾添加time.Sleep(),但是这样会引发两个问题:
- 等待多长时间?
- 时间太长,影响性能?
在go的sync库中的WaitGroup可以帮助我们完成此项工作,Add(n)把计数器设置为n,Done()会将计数器每次减1,Wait()函数会阻塞代码运行,直到计数器减0。
等待多个goroutine完成,可以使用一个等待组。 例如:
// 这是我们将在每个goroutine中运行的函数。
// 注意,等待组必须通过指针传递给函数。
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
}
这里首先把wg 计数设置为1, 每个for循环运行完毕都把计数器减一,主函数中使用Wait() 一直阻塞,直到wg为1——也就是所有的5个for循环都运行完毕。
使用注意点:
- 计数器不能为负值
- WaitGroup对象不是引用类型
2.Once
sync.Once可以控制函数只能被调用一次,不能多次重复调用。
例如:
var doOnce sync.Once
func main() {
DoSomething()
DoSomething()
}
func DoSomething() {
doOnce.Do(func() {
fmt.Println("Run once - first time, loading...")
})
fmt.Println("Run this every time")
}
输出:
Run once - first time, loading...
Run this every time
Run this every tim
3.互斥锁Mutex
互斥锁是并发程序对共享资源进行访问控制的主要手段,在go中的sync中提供了Mutex的支持。
例如:使用互斥锁解决多个Goroutine访问同一变量。
// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
defer c.mux.Unlock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
}
// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
defer c