基础概念
- 并发:多线程程序在单核上运行
- 并行:多线程程序在多核上运行
- go1.8之前,go程序的cpu数需要手动设置。1.8之后默认让程序运行在多核上,可以不设置
https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/goroutine
Go协程特点
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 协程是轻量级的线程(编译器做优化)
协程注意点
- 主线程是一个物理线程,直接作用在cpu上的,是重量级的,非常耗费cpu资源
- 协程从主线程开启的,是轻量级的,是逻辑太,对资源消耗相对小
- 如果主线程退出了,则协程即使还没有执行完毕,也会退出
- go的协程机制是重要的特点,可以轻松的开启上完个协程。其他编程语言的并发机制一般是基于现场,开启过多的线程,资源消费大
错误处理
goroutine发生错误的时候,通过panic和recover来解决。如果不处理会导致整个程序崩溃
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
| package main
import ( "fmt" "time" )
func test() { defer func() { if err := recover(); err != nil { fmt.Println("test发生错误", err) } }()
var map1 map[int]string map1[0] = "1111" }
func main() { go test()
for { fmt.Println("....") time.Sleep(time.Second) } }
|
MPG调度模型
- M:操作系统的线程(物理线程)
- P:协程执行需要的上下文
- G:协程
channel
为什么需要channel
- 主线程在等待所有goroutine全部完成的时间很难确定,仅仅是估算
- 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处理工作状态,这时也会随主线程的退出而销毁
- 通过全局变量加锁同步实现通讯,也并不利于多个协程对全局变量的读写操作
channel要点
- channel本质就是一个数据结构-队列
- 数据是先进先出
- 线程安全,多个goroutine访问时,不需要加锁
- channel是有类型的,一个string的channel只能存放string类型数据
- channel是引用类型
- channel必须初始化才能写入数据,即make后才能使用
- 当给channel写入数据时,不能超过容量,不然会报错,也就是不会动态扩展
- 当没有channel没有数据时,进行取数据会阻塞
- 如果想放任意类型,可以使用interface类型,但是不建议
- channel被关闭后,只能读不能写
- channel可以声明为只可读或只可写
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 39 40 41 42 43 44 45 46 47 48
| package main
import ( "fmt" )
func recv(ch <-chan int){
}
func send(ch chan<- int) { }
func main() { var intChan chan int intChan = make(chan int, 3)
fmt.Printf("intChan=%v addr=%p\n", intChan, &intChan)
intChan<- 10 num := 211 intChan<- num
fmt.Printf("intChan len=%v cap=%v\n", len(intChan), cap(intChan))
var num2 int num2 = <-intChan
fmt.Println(num2) fmt.Printf("intChan len=%v cap=%v\n", len(intChan), cap(intChan))
close(intChan)
var readChan <-chan int; var writeChan chan<- int; }
|
channel遍历要点
- channel支持for-range进行遍历
- 在遍历时,如果channel没有关闭,则会出现deadlock的错误
- 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
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
| package main
import ( "fmt" "time" )
var intChan chan int
func closeChan() { time.Sleep(time.Second * 5) close(intChan) }
func main() { intChan = make(chan int, 100) for i := 0; i < 100; i++ { intChan<- i * 2 }
var sec1 = time.Now().Unix()
go closeChan()
for v := range intChan { fmt.Println("v=", v) }
var sec2 = time.Now().Unix()
fmt.Println("等待时间", sec2 - sec1) }
|
select
在读取没有关闭的channel的时候,如果没有数据,会一直死锁,可以通过select来解决。
select要点
- select不能直接用break跳出,只能通过标签或return跳出
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" )
func main() { intChan := make(chan int, 10) for i := 0; i < 10; i++ { intChan<- i }
stringChan := make(chan string, 5) for i := 0; i < 5; i++ { stringChan<- fmt.Sprintf("hello %d", i) }
label1: for { select { case v := <-intChan: fmt.Printf("从intChan读到数据%v\n", v) case v := <-stringChan: fmt.Printf("从stringChan读到数据%v\n", v) default: fmt.Println("都读取不到数据") break label1 } }
close(intChan) close(stringChan)
fmt.Println("退出") }
|
数据交换
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 39 40 41 42 43 44 45 46 47
| package main
import ( "fmt" )
func readData(intChan chan int, exitChan chan bool) { for { v, ok := <-intChan if !ok { break }
fmt.Printf("readData 读到数据 %v\n", v) }
exitChan<- true close(exitChan) }
func writeData(intChan chan int) { for i := 1; i < 50; i++ { intChan<- i fmt.Printf("writeData 写入数据 %v\n", i) }
close(intChan) }
func main() { intChan := make(chan int, 50) exitChan := make(chan bool, 1)
go writeData(intChan) go readData(intChan, exitChan)
for { _, ok := <-exitChan if !ok { break } }
fmt.Println("读写操作完成!!") }
|
Cond
Cond实现了一种条件变量,主要用来解决多个读协程等待共享资源变成ready的场景。在使用Cond的时候,需要特别注意下:每个Cond都会关联一个Lock(*sync.Mutex or *sync.RWMutex)
,当修改条件或者调用Wait方法时,必须加锁,保护condition。
Map
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
| package main
import ( "fmt" "sync" )
func main() { var scene sync.Map
scene.Store("greece", 97) scene.Store("london", 100) scene.Store("egypt", 200)
fmt.Println(scene.Load("london"))
scene.Delete("london")
scene.Range(func(k, v interface{}) bool { fmt.Println("iterate:", k, v) return true }) }
|