1. Go 并发编程(Goroutine、Channel、Sync)
1.1 并发 VS 并行
并发:多线程程序在一个核的cpu上运行(是指多个任务在一段时间内同时发生 )(并发不一定是同时发生,可能是同一时间段内交替发生 )
并行:多线程程序在多个核的cpu上运行(多个任务同时发生 )
Go可以充分发挥多核优势,高效运行
1.2 Goroutine
协程:用户态,轻量级线程,栈KB级别
线程:内核态,线程跑多个协程,栈MB级别
快速打印hello goroutine:0~hello goroutine:4
快速打印:使用协程,同一线程上并行
time.Sleep(time.Second) // 防止主协程直接结束了,打印协程还没来得及执行
package main
import (
"fmt"
"time"
)
func hello(i int){
println("hello goroutine:"+fmt.Sprint(i))
}
func main(){
for i:=0;i<5;i++{
go func(j int){
hello(j)
}(i)
}
time.Sleep(time.Second) // 防止主协程直接结束了,打印协程还没来得及执行
}
// before go coroutine
// after go coroutine
// hello goroutine:4
// hello goroutine:1
// hello goroutine:3
// hello goroutine:0
// hello goroutine:2
1.3 CSP (Communicating Sequential Processes)
CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,是一种并发编程模型,是一个很强大的并发数据模型
用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型
GO语言并没有完全实现了CSP模型的所有理论,仅仅是借用了 process和channel这两个概念。
process是在go语言上的表现是:goroutine 是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享
提倡通过通信共享内存而不是通过共享内存而实现通信
1.4 Channel
Goroutine和Channel是Go语言并发编程的 两大基石。Goroutine用于执行并发任务,Channel用于Goroutine 之间的同步、通信
Channel 在 gouroutine 间架起了一条管道,在管道里传输数据,实现 gouroutine 间的通信;由于它是线程安全的,所以用起来非常方便;channel 还提供 “先进先出” 的特性;它还能影响 goroutine 的阻塞和唤醒
- Channel 是 Go 语言中一个非常重要的类型,是 Go 里的第一对象。通过 channel,Go 实现了通过通信来实现内存共享。Channel 是在多个 goroutine 之间传递数据和同步的重要手段
- Channel 分为两种:带缓冲、不带缓冲。对不带缓冲的 channel 进行的操作实际上可以看作 “同步模式”,带缓冲的则称为 “异步模式”
make(chan 元素类型,[缓冲大小])
无缓冲通道:make(chan int)
有缓冲通道:make(chan int,2)
package main
func main(){
src := make(chan int) // 无缓冲
dest := make(chan int,3) // 有缓冲
go func(){
/* A协程:生产数字,将数字发送到src channel */
defer close(src)
for i:=0;i<10;i++{
src <- i
}
}()
go func(){
/* B协程:遍历src数据,通过src channel实现A协程与B协程通信,计算平方,将数字发送到dest channel */
defer close(dest)
for i := range src{
dest <- i * i
}
}()
/* 主协程:遍历dest数据,进行复杂操作(此处为打印操作) */
for i := range dest{
// 复杂操作
println(i)
}
}
// 0
// 1
// 4
// 9
// 16
// 25
// 36
// 49
// 64
// 81
1.5 并发安全Lock
并发安全Lock 对变量执行2000次操作,5个协程并发执行
package main
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock(){
for i:=0;i<2000;i++{
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock(){
for i:=0;i<2000;i++{
x +=1
}
}
func main(){
x = 0
for i:=0;i<5;i++{
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:",x)
x = 0
for i:=0;i<5;i++{
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock:",x)
}
// WithoutLock: 7465
// WithLock: 10000
1.6 WaitGroup
sync.WaitGroup 是 Go 语言标准库中的一个结构体,用于等待一组 goroutine 完成执行。它的主要作用是等待所有的 goroutine 完成后再继续执行下一步操作,以避免主程序过早退出
sync.WaitGroup 结构体中的 state1 字段包含了一个 counter 计数器,用于记录等待的 goroutine 数量
func ManyGoWait(){
var wg sync.WaitGroup
wg.Add(5)
for i:=0;i<5;i++{
go func(j int){
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
// hello goroutine:4 hello goroutine:2 hello goroutine:3 hello goroutine:0 hello goroutine:1
-
sync.WaitGroup 的方法
-
Add 方法
Add 方法用于向 WaitGroup 中添加指定数量的等待的 goroutine
func (wg *WaitGroup) Add(delta int) delta 表示要添加的等待的 goroutine 的数量
Add 方法会将 delta 值加到 counter 上
-
Done 方法
Done 方法用于标记一个等待的 goroutine 已经完成
func (wg *WaitGroup) Done() Done 方法会将 counter 减 1
-
Wait 方法
Wait 方法用于阻塞当前的 goroutine,直到所有的等待的 goroutine 完成
func (wg *WaitGroup) Wait() Wait 方法会检查 counter 的值,如果不为 0,则当前的 goroutine 会被阻塞
当 counter 的值为 0 时,阻塞解除,当前的 goroutine 可以继续执行
-