12.并发
并发和并行
并发:同一时间内执行多个任务
并行:同一时刻执行多个任务
goroutine
goroutine的概念类似于线程,但不需要自己去写进程、线程、协程,只需要把任务包装成一个函数,开启一个goroutine去执行这个函数就可以了
使用goroutine
只需要在调用函数的时候,前边加上go关键字,就可以为一个函数创建一个goroutine,一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数
启动单个goroutine
func hello(){
fmt.Println("Hello")
}
func main(){
go hello() // 开启一个独立的goroutine去执行hello这个函数
fmt.Println("主线程done!")
}
上面这个例子可能会只打印主线程done!不打印Hello,原因是在程序启动的时候,Go程序会为主函数main()创建一个goroutine,主函数结束后,会把所有子goroutine关掉,而在上面的例子中,main()函数结束的太快了,在hello()执行前就结束了,所以没有打印Hello。
可以使用WaitGroup来解决
package main
import (
"fmt"
"sync"
)
// 声明WaitGroup变量
var wg sync.WaitGroup
func hello() {
fmt.Println("Hello")
wg.Done() // 标记任务结束
}
func main() {
wg.Add(1) // 计数器+1
go hello()
fmt.Println("主线程")
wg.Wait() // 等待所有任务结束
}
启动多个goroutine
package main
import (
"fmt"
"sync"
)
// 声明WaitGroup变量
var wg sync.WaitGroup
func hello() {
fmt.Println("Hello")
wg.Done() // 标记任务结束
}
func main() {
for i:=0; i<100;i++{
wg.Add(1) // 计数器+1
go hello()
}
fmt.Println("主线程")
wg.Wait() // 等待所有任务结束
}
使用匿名函数启动goroutine
package main
import (
"fmt"
"sync"
)
// goroutine
var wg sync.WaitGroup
func main() {
for i := 0; i < 10000; i++ {
wg.Add(1)
go func() {
fmt.Println("hello", i)
wg.Done()
}()
}
fmt.Println("主程序done")
wg.Wait()
}
GOMAXPROCS()
runtime.GOMAXPROCX(1)指定几个物理核心来执行
go 1.5以后得版本默认有多少核心就使用多少核心
channel
channel是一种引用类型,与队列性质相同,遵循先进先出原则。通道类型的空值是nil,通道声明以后需要使用make函数初始化后才可以使用,声明格式如下:
var 变量名称 chan 元素类型
var ch1 chan int // 声明一个传递整形的通道
var ch2 chan bool // 声明一个传递bool类型的通道
var ch3 chan []int// 声明一个传递int切片的通道
make(chan 元素类型,[缓冲大小]) // 缓冲大小可以不写
ch4:= make(chan int)
ch5:= make(chan bool, 1)
channel 操作
package main
import "fmt"
// channel demo
func main() {
// 声明一个int类型的channel
var ch1 chan int
// 初始化channel
ch1 = make(chan int, 1)
// 往通道中存放一个值
ch1 <- 10
fmt.Println(ch1)
// 从通道中取出一个值
x := <-ch1
fmt.Println(x)
// 取通道中元素的数量
len(ch1)
// 拿到通道的容量
cap(ch1)
// 这两个操作几乎不会用到
// 关闭通道
close(ch1) // 关闭操作不是必须的,无用通道会被垃圾回收掉
}
无缓冲区的通道
ch := make(chan int)
ch1 <- 10 // 报错,deadlock
无缓冲区的通道(同步通道)和带缓冲区通道的区别
无缓冲的通道只有在接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的状态。
有缓冲的通道可以在通道内存放指定数量的数据,等待接收方来取,通道内存放满时,便不能再存放了
goroutine和channel协同
// 生成两个goroutine,两个channel
// 1. 生成0-100的数字发送到ch1
// 2. 从ch1中取出数据计算它的平方,把结果放到ch2中
package main
import "fmt"
// 1.生成0-100的数字发送到ch1
func f1(ch chan int) {
for i := 0; i < 100; i++ {
ch <- i
}
close(ch) // 通道关闭后也可以继续取值
}
// 2. 从ch1中取出数据计算它的平方,把结果放到ch2中
func f2(ch1, ch2 chan int) {
for {
tem, ok := <-ch1
if ok {
ch2 <- tem * tem
} else {
break
}
}
close(ch2)
}
func main() {
ch1 := make(chan int, 100)
ch2 := make(chan int, 200)
go f1(ch1)
go f2(ch1, ch2)
// 可以通过for range的方式从通道中取值
for ret := range ch2 {
fmt.Println(ret)
}
}
单向通道
- 只接收的通道
func f1(ch1 chan<- int){ // 在形参的类型后增加标识,表示只存
x := <-ch1 // 会报错
}
- 只取值的通道
func f2(ch2 <-chan int){ // 在形参的类型前增加标识,表示只取
ch2 <- 1 // 报错
}
通道状态总结

对已经关闭的通道再次执行 close,也会引发 panic
select 多路复用
select 类似于switch 语句
select{
case <-ch1:
...
case data := <-ch2:
...
case ch3<-1:
...
}
- 可处理一个或多个channel的发送/接收操作
- 如果多个case同时满足,select会随机选择一个
- 对于没有case的select会一直等待,可用于阻塞main函数
并发安全和锁
多个goroutine同时操作同一个资源的时候,会发生资源冲突
var x int
var wg sync.WaitGroup
func add(){
for i:=0;i<5000;i++{
x = x+1
}
wg.Done()
}
func main(){
wg.Add(2)
// 开启两个goroutine,同时操作同一个全局变量
go add()
go add()
wg.Wait()
fmt.Println(x) // 正常的运行结果应该是10000,但实际可能不到10000
}
互斥锁sync.Mutex
保证同一时间内只有一个goroutine能够获得锁
package main
import (
"fmt"
"sync"
)
var x int
var wg sync.WaitGroup
var lock sync.Mutex // 互斥锁
func add() {
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
// 开启两个goroutine,同时操作同一个全局变量
go add()
go add()
wg.Wait()
fmt.Println(x) // 正常的运行结果应该是10000,但实际可能不到10000
}
读写互斥锁sync.RWMutex
互斥锁是完全互斥的,但很多场景下是读多写少的
读写锁分为两种:读锁和写锁。当一个goroutine获取读锁以后,其他的goroutine如果获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获得写锁后,其他goroutine无论获取什么锁都会等待。
package main
import (
"fmt"
"sync"
"time"
)
var (
x int
wg sync.WaitGroup
rw sync.RWMutex
)
func read() {
rw.RLock() // 只有读取的时候加的锁不一样
time.Sleep(time.Millisecond)
rw.RUnlock() // 只有读取的时候加的锁不一样
wg.Done()
}
func write() {
rw.Lock()
x = x + 1
time.Sleep(time.Millisecond * 10)
rw.Unlock()
wg.Done()
}
func main() {
start := time.Now()
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
wg.Wait()
fmt.Printf("%s\n", time.Since(start))
}
sync.Once
保证函数只执行一次,可以用来初始化或者关闭资源(比如多个goroutine关闭channel)
var icons map[string]image.Image
var loadIconsOnce sync.Once
func loadIcons(){
icons = map[string]image.Image{
"left": loadicon("left.png"),
"right": loadicon("right.png"),
"up": loadicon("up.png"),
"down": loadicon("down.png"),
}
}
func Icon(name string) image.Image{
loadIconsOnce.Do(loadIcons) // 保证初始化icons的操作只执行一次
return icons[name]
}
sync.Map
go语言中内置的map不是并发安全的
package main
import (
"fmt"
"sync"
)
var m = make(map[int]int)
var wg sync.WaitGroup
func get(key int) int {
return m[key]
}
func set(key int, value int) {
m[key] = value
}
func main() {
for i := 0; i < 20; i++ {
wg.Add(1)
go func(i int) {
set(i, i+100)
fmt.Printf("key:%v, value:%v", i, get(i))
wg.Done()
}(i)
}
wg.Wait()
}
以上这段代码执行会报错,因为go语言中内置的map不是并发安全的,这种场景可以自己加锁来实现,也可以直接使用syncn.Map
package main
import (
"fmt"
"sync"
)
var m = sync.Map{}
var wg sync.WaitGroup
func main() {
for i := 0; i < 20; i++ {
wg.Add(1)
go func(i int) {
m.Store(i, i+100)// sync.Map中写数据
value, _ := m.Load(i) // 读取sync.Map的值
fmt.Printf("key:%v, value:%v\n", i, value)
wg.Done()
}(i)
}
wg.Wait()
}
sync.Map的打印(Range方法)
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// 添加数据
m.Store("name", "Alice")
m.Store("age", 30)
m.Store("job", "Engineer")
// 打印 sync.Map
fmt.Print("sync.Map contents: {")
first := true
m.Range(func(key, value interface{}) bool {
if !first {
fmt.Print(", ")
}
fmt.Printf("%v: %v", key, value)
first = false
return true
})
fmt.Println("}")
}

浙公网安备 33010602011771号