go并发编程
预备知识
进程和线程
并发和并行
golang设置CPU

协程 - goroutine
- 一个Go主线程(也可以理解为进程)上可以起很多的协程
- 协程是轻量级线程,底层根据线程特点进行了大量优化[编译器底层优化]
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 协程是轻量级的线程
MPG模式



管道 - channel
基本使用
//声明管道var intChan chan int //intChan用于存放int数据//创建一个可以存放3个int类型的管道intChan = make(chan int, 3)//向管道写入数据num := 211intChan<- 100intChan<- num//从管道中读取数据var num2 intnum2 = <-intChanfmt.Println(num2)fmt.Printf("channel len=%v cap=%v", len(intChan), cap(intChan))
channel的遍历和关闭

//管道的遍历 - for rangeintChan2 := make(chan int, 100)for i := 0; i < 100; i++{intChan2<- i}close(intChan2) //关闭管道//没close 死锁错误for v := range intChan2{fmt.Println("v = ", v)}
并发安全问题
并发写问题
多个协程同时修改同一全局变量
//全局变量var (myMap = make(map[int]int, 10))// 使用goroutine计算 1-200 各个数的阶乘,并把各个数的阶乘放到 map 中func Demo3() {//启用200个协程for i := 1; i <= 200; i++{go channelTest(i)}time.Sleep(time.Second * 10)for key, value := range myMap {fmt.Printf("%v! = %v", key, value)}}// 计算n的阶乘,将结果放入到map中func channelTest(n int){res := 1for i := 1; i<=n ; i++{res *= i}myMap[n] = res}

使用 go build -race 编译可检查是否有数据冲突(data race)
使用互斥锁(初级)
//全局变量 lock sync.Mutex
//使用互斥锁lock.Lock()myMap[n] = reslock.Unlock()
var (//全局变量myMap = make(map[int]int, 10)//全局变量 互斥锁lock sync.Mutex)// 使用goroutine计算 1-200 各个数的阶乘,并把各个数的阶乘放到 map 中func Demo3() {//启用200个协程for i := 1; i <= 200; i++{go channelTest(i)}time.Sleep(time.Second )for key, value := range myMap {fmt.Printf("%v! = %v\n", key, value)}}// 计算n的阶乘,将结果放入到map中func channelTest(n int){res := 1for i := 1; i<=n ; i++{res *= i}//使用互斥锁lock.Lock()myMap[n] = reslock.Unlock()}
协程结合管道

package goroutineDemoimport ("fmt")// 一个管道,两个协程// 一个读,一个写// 主线程需要等待协程操作完成再关闭func Channel1() {// 创建两个管道intChan := make(chan int, 50) //数据管道exitChan := make(chan bool, 1) //退出管道go writeData(intChan)go readData(intChan, exitChan)//time.Sleep(time.Second * 10)//等待协程for{_ , ok := <-exitChanif !ok{break}}}func writeData(intChan chan int){for i:=1; i<=50; i++{intChan<- ifmt.Println("写入数据 ",i)}close(intChan)}func readData(intChan chan int,exitChan chan bool){for {v,ok := <-intChanif !ok {break}fmt.Printf("readData %v\n",v)}//告知主线程exitChan<- trueclose(exitChan)}

注意事项
- channel中只能存放指定的数据类型
- channel的数据放满后再放,会报死锁错误
- 单线程 - channel没有close的情况下:channel中的数据为空再取,会报死锁错误
- 单线程 - channel已经close的情况下:channel中的数据为空再取,v,ok = <-channel 中 ok会被置为false
- 多线程 - 多线程情况下,同一管道如果只读不写或者只写不读,编译器会报错 deadlock!
- 多线程 - 多线程情况下,同一管道如果读写速率不一致,则速率快的会有效阻塞
- 只读管道 var intChan <-chan int ; 只写管道 :var intChan chan<- int
只读/只写管道可以在函数传参时限制函数内的操作方式
使用select可以解决从管道取数据的阻塞问题
//使用select解决从管道取数据的阻塞问题//容量为10的int管道intChan := make(chan int,10)for i := 0; i < 10; i++ {intChan<- i}//容量为5的string管道stringChan := make(chan string, 5)for i := 0; i < 5; i++ {stringChan<- "hello "+fmt.Sprintf("%d",i)}//传统方法遍历时,如果没有close管道,会阻塞导致死锁//for {// v := <-intChan// fmt.Println(v)//}for {select {//此种方式,即使管道一直不关闭,也不会一直阻塞导致deadlock//会自动到下一个case匹配case v := <-intChan :fmt.Printf("从intChan中读取数据%d\n",v)case v := <-stringChan :fmt.Printf("从stringChan中读取数据%s\n",v)default:fmt.Println("都取不到了,不玩了!!!")return}}

goroutine中使用defer+recover,可以解决单个协程出错影响其他协程以及主线程的问题 ```go // 一个协程报错导致程序崩溃 func RecoverDemo() { go sayHello() go errorFunc()
time.Sleep(time.Second * 5) }
func sayHello(){ for i := 0; i < 10; i++{ time.Sleep(time.Second) fmt.Println(“hello golang”) } }
func errorFunc(){ var myMap map[int]string //没有make,直接赋值 myMap[0] = “golang” }
```go// defer + recoverfunc errorFunc(){defer func() {//捕获errorFunc抛出的panicif err := recover(); err!=nil{fmt.Println("函数错误!!",err)}}()var myMap map[int]string//没有make,直接赋值myMap[0] = "golang"}



