go语言读书笔记-aciton系列(并发)

终于到并发了,阅读本章总有种go对并发的处理简洁高效的感觉。action系列举的实战例子也很通俗易懂,感觉运用一些实际的项目实践应该理解会更加深刻。

goroutine

操作系统会在物理处理器上调度线程来运行程序,而Go语言运行时会在逻辑处理器上调用goroutine来运行。每个逻辑处理器都分别绑定到单个操作系统线程。
em。。goroutine可以理解为携程,就像这样。
image_1d6fdftt11ti71crn1qn2c791uls9.png-230.1kB
携程运行于线程之上,比线程更加轻量级。goroutine运行于逻辑处理器,而逻辑处理器就对应一个操作系统线程,其可以并发调度无数个groutine。

goroutine也可以并行运行,只要使用超过一个的逻辑处理器。但是只要底层硬件层面只有一个处理器,即使创建了多个逻辑处理器也依然是并发运行。
image_1d6felqb512qn1r251k38110tchqm.png-183.9kB
并发并行的区别正如书上的图所示。
通过设置参数可以给每个可用的物理处理器分配一个逻辑处理器,从而达到并行的目的。

1
2
3
import "runtime"

runtime.GOMAXPROCS(runtime.NumCpu())

同步goroutine

在写并发代码时,往往都会遇到竞争状态的问题。这就需要采用一些同步手段来得到正确的结果。go语言提供了锁的机制来同步。

原子函数

go sync包提供了一些常用的操作的原子函数。当使用这些函数来读,写时,其都会自动根据所引用的变量做同步处理。已addint64函数示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"runtime"
"sync"
"sync/atomic"
)
var (
counter int64
wg sync.WaitGroup
)

func main() {
wg.Add(2)

go incCounter(1)
go incCounter(2)

wg.Wait()

fmt.Println("Final Counter:",counter)
}


func incCounter(id int){
defer wg.Done()
for count :=0; count<2;count++{
atomic.AddInt64(&counter,1)
runtime.Goshed()
}
}

程序一开始创建两个goroutine形成对counter变量的竞争条件。而AddInt64函数则强制同一时刻 只能有一个goroutine运行并完成这个加法操作。当goroutine试图取d去调用任何原子函数时,这些goroutine都会自动根据所引用的变量做同步处理。

互斥锁

互斥锁就和我们通常使用的许多语言中的锁机制一样,在代码中创建一个临界区,保证同一时间只有一个goroutine可以执行这个临界区的代码。
对刚刚的程序使用锁机制同步就是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"fmt"
"runtime"
"sync"
"sync/atomic"
)
var (
counter int64
wg sync.WaitGroup
)

func main() {
wg.Add(2)

go incCounter(1)
go incCounter(2)

wg.Wait()

fmt.Println("Final Counter:",counter)
}


func incCounter(id int){
defer wg.Done()
for count :=0; count<2;count++{
mutex.Lock()
{
value := counter
runtime.Gosched()
value++
counter = value
}
mutex.UnLock()
}
}

同一时刻只能有一个goroutine可以进入临界区。之后,直到调用Unlock()函数之后,其他goroutine才能进入临界区。

通道

go语言中提供了通道,通过发送和接收需要共享的资源,在goroutine之间做同步。
通道分为无缓冲通道和有缓冲的通道。无缓冲的通道是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。这种对通道进行发送和接收的交互行为本身就是同步的。

有缓冲的通道是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求goroutine之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值的时,发送动作才会阻塞。这导致有缓冲的通道和无缓冲的通道之间有一个很大的不同:无缓冲的通道保证进行发送和接收的goroutine会在同一时间进行数据交换了;有缓冲的通道没有这种保证。

感悟

至此,对于goaction系列中对于go语言语法特性的部分,就算是阅读完毕了。书中举了很多这些语法的实际应用场景例子,但是实际掌握还是需要自己动手写以及多看看别人项目的源代码,个人感觉这样才能得到最快的提升。所以接下来的action章节读书笔记,就主要记录书中实际的demo吧。