假设我们现在有这么一个需求: 计算1-200之间各个数的阶乘,并将每个结果保存在map中,最终显示出来,要求使用goroutine。 分析: (1)使用goroutine完成,效率高,但是会出现并发/并行安全问题; (2)不同协程之间如何通信; 对于(1):不同协程之间可能同时对一块内存进行操作,导致数据的混乱,即并发/并行不安全;主协程运行完了,计算阶乘的协程却没有运行完,功能并不能够准确实现;可利用互斥锁解决该问题; 对于(2):可以利用利用管道; 正常的代码: 复制代码 package main import ( "fmt" "sync" ) var ( myMap = make(map[int]int, 10) ) func cal(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } myMap[n] = res } func main() { for i := 1; i <= 15; i++ { go cal(i) } for i, v := range myMap { fmt.Printf("map[%d]=%d\n", i, v) } } 复制代码 运行结果:会报错 1.利用互斥锁  复制代码 package main import ( "fmt" "sync"   ""   ) var ( myMap = make(map[int]int, 10) //lock是全局互斥锁,synchornized lock sync.Mutex ) func cal(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } lock.Lock() myMap[n] = res lock.Unlock() } func main() { for i := 1; i <= 15; i++ { go cal(i) } for i, v := range myMap { fmt.Printf("map[%d]=%d\n", i, v) } } 复制代码 有可能主程序运行完了而cal还没运行完(上面结果只到13,没有14,15),需要加上time.Sleep(time.Seconde*3),而在输出时,由于主协程并不知道程序已经完成了,底层仍然可能出现竞争资源,所以在输出阶段也要加上互斥锁。最终代码如下: 复制代码 package main import ( "fmt" "sync" ) var ( myMap = make(map[int]int, 10) //lock是全局互斥锁,synchornized lock sync.Mutex ) func cal(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } lock.Lock() myMap[n] = res lock.Unlock() } func main() { for i := 1; i <= 15; i++ { go cal(i) } time.Sleep(time.Second * 4) lock.Lock() for i, v := range myMap { fmt.Printf("map[%d]=%d\n", i, v) } lock.Unlock() } 复制代码 为什么需要管道? (1)主线程在等待所有协程全部完成的时间很难确定; (2)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能协程还处于工作状态,这时也会随着主协程的结束而销毁; (3)通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量的读写操作; 管道的介绍: (1)管道的本质就是一种数据结构--队列; (2)数据先进先出; (3)线程安全,多协程访问时,不需要加锁; (4)管道只能存储相同的数据类型; 管道的声明: var intChan chan int; var stringChan chan string; var mapChan chan map[int]string; var perChan chan Person; var perChan chan *Person; 注意:管道是引用类型;管道必须初始化后才能写入数据;管道是有类型的,即IntChan只能写入int; 管道初始化: var intChan chan int intChan = make(chan int,10)  向管道中读写数据: num := 10 intChan<-num var num2 int num2<-intChan 注意:管道容量满了则不能继续写入,在没有使用协程的情况下,管道空了不能继续读取。 如何使管道中存储任意数据类型? channel的关闭: 使用内置的close可以关闭管道,关闭后不能再进行写入,但是可以进行读取; channel的遍历: channel可以使用for range进行遍历 ,但是要注意: 在遍历时,如果channel没有关闭,则会出现deadlock错误; 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完成后退出;(即在遍历前需要先关闭管道) 2.利用管道实现边写边读 流程图: 复制代码 package main import ( "fmt" ) var ( myMap = make(map[int]int, 10) ) func cal(n int) map[int]int { res := 1 for i := 1; i <= n; i++ { res *= i } myMap[n] = res return myMap } func write(myChan chan map[int]int) { for i := 0; i <= 15; i++ { myChan <- cal(i) fmt.Println("writer data:", cal(i)) } close(myChan) } func read(myChan chan map[int]int, exitChan chan bool) { for { v, ok := <-myChan if !ok { break } fmt.Println("read data:", v) } exitChan <- true close(exitChan) } func main() { var myChan chan map[int]int myChan = make(chan map[int]int, 20) var exitChan chan bool exitChan = make(chan bool, 1) go write(myChan) go read(myChan, exitChan) for { _, ok := <-exitChan if !ok { break } } } 复制代码 结果: 思考:假设我们注销掉go read(myChan,exitChan)会发生什么呢? 也就是说,只有写入myChan而没有读取myChan,当存入myChan里面的数据达到了myChan的容量,再继续存入就会报deadlock错误。同时,由于exitChan需要写入一个true,而exitChan需要读取完myChan中的数据后才写入一个true,但是现在不能进行读取,也就是说,true不会写入exitChan,就形成了阻塞。假设我们打开go read(myChan,exitChan),我们设置其每隔1秒才读取一条数据,而写入则让其正常运行,也就是说,写入很快,读取很慢,这样会导致deadlock吗?答案是不会,只要有读取,golang会有个机制,不会让myChan存储的值超过myChan的容量。 管道的使用注意事项: (1)在默认情况下,管道是双向的。管道是可以声明是只读还是只写;   var intChan chan<-int(只写)   intChan = make(chan int,3)   var intChan2 <-chan int (2)使用select可以解决从管道取数据阻塞问题; 复制代码 func Test2() { intChan := make(chan int, 10) for i := 0; i < 10; i++ { intChan <- i } strChan := make(chan string, 5) for i := 0; i < 5; i++ { strChan <- "hello" + fmt.Sprintf("%d", i) } //传统方法是可用close关闭,但是当不知道什么时候需要关闭时,这就不可用 //实际开发中可以使用select解决 for { select { case v := <-intChan: fmt.Printf("从intChan中读取数据%d\n", v) case v := <-strChan: fmt.Printf("从strChan中读取数据%s\n", v) default: fmt.Println("都取不到数据了") return } } } 复制代码 运行结果: (4)goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题。 说明:如果我们建立了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,则会造成整个程序的崩溃,这时,我们可以在goroutine中使用recover来捕获panic,进行处理,这样即使这个协程发生了问题,但是主线程仍然不受影响。 复制代码 package main import ( "fmt" "time" ) func sayHello() { for i := 0; i < 10; i++ { time.Sleep(time.Millisecond * 3) fmt.Println("hello") } } func test() { //这里我们可以使用defer revover解决nil defer func() { if err := recover(); err != nil { fmt.Println("test()发生错误,error=", err) } }() var myMap map[int]string myMap[0] = "golang" } func main() { go sayHello() go test() for i := 0; i < 10; i++ { time.Sleep(time.Millisecond * 3) fmt.Println("main() ok=", i) } }https://www.cnblogs.com/xiximayou/p/11953584.html