Golang-goroutine/channel
Posted essaycode
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang-goroutine/channel相关的知识,希望对你有一定的参考价值。
goroutine-基本介绍
进程和线程介绍
程序、进程和线程的关系示意图
并发和并行
1)多线程程序在单核上运行,就是并发
2)多线程程序在多核上运行,就是并行
3)示意图:
小结:
Go 协程和Go 主线程
Go 主线程(有程序员直接称为线程/也可以理解成进程): 一个 Go 线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程[编译器做优化]。
Go 协程的特点
1)有独立的栈空间
2)共享程序堆空间
3)调度由用户控制
4)协程是轻量级的线程
示意图
goroutine-快速入门
案例说明
?请编写一个程序,完成如下功能:
1)在主线程(可以理解成进程)中,开启一个 goroutine, 该协程每隔 1 秒输出 "hello,world"
2)在主线程中也每隔一秒输出"hello,golang", 输出 10 次后,退出程序
3)要求主线程和 goroutine 同时执行.
4)画出主线程和协程执行流程图
输出的效果说明, main 这个主线程和 test 协程同时执行.
主线程和协程执行流程图
快速入门小结
1)主线程是一个物理线程,直接作用在 cpu 上的。是重量级的,非常耗费 cpu 资源。
2)协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
3)Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显 Golang 在并发上的优势了
goroutine 的调度模型
MPG 模式基本介绍
MPG 模式运行的状态 1
MPG 模式运行的状态 2
设置 Golang 运行的cpu 数
介绍:为了充分了利用多 cpu 的优势,在 Golang 程序中,设置运行的 cpu 数目
channel(管道)-看个需求
需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到 map 中。最后显示出来。要求使用 goroutine 完成
分析思路:
1)使用 goroutine 来完成,效率高,但是会出现并发/并行安全问题.
2)这里就提出了不同 goroutine 如何通信的问题
代码实现
1)使用 goroutine 来完成(看看使用 gorotine 并发完成会出现什么问题? 然后我们会去解决)
2)在运行某个程序时,如何知道是否存在资源竞争问题。 方法很简单,在编译该程序时,增加一个参数 -race 即可 [示意图]
package main import ( "fmt" "time" ) // 需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到 map 中。 // 最后显示出来。要求使用 goroutine 完成 // 思 路 // 1. 编写一个函数,来计算各个数的阶乘,并放入到 map 中. // 2. 我们启动的协程多个,统计的将结果放入到 map 中 // 3. map 应该做出一个全局的. var ( myMap = make(map[int]int, 10) ) // test 函数就是计算 n!, 让将这个结果放入到 myMap func test(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } //这里我们将 res 放入到 myMap myMap[n] = res //concurrent map writes? } func main() { // 我们这里开启多个协程完成这个任务[200 个] for i := 1; i <= 200; i++ { go test(i) } //休眠 10 秒钟【第二个问题 】 time.Sleep(time.Second * 10) //这里我们输出结果,变量这个结果 for i, v := range myMap { fmt.Printf("map[%d]=%d ", i, v) } }
示意图
为什么需要channel
package main import ( "fmt" ) func main() { //演示一下管道的使用 //1. 创建一个可以存放 3 个 int 类型的管道 var intChan chan int intChan = make(chan int, 3) //2. 看看 intChan 是什么 fmt.Printf("intChan 的值=%v intChan 本身的地址=%p ", intChan, &intChan) //3. 向管道写入数据 intChan<- 10 num := 211 intChan<- num intChan<- 50 // intChan<- 98//注意点, 当我们给管写入数据时,不能超过其容量 //4. 看看管道的长度和 cap(容量) fmt.Printf("channel len= %v cap=%v ", len(intChan), cap(intChan)) // 3, 3 //5. 从管道中读取数据 var num2 int num2 = <-intChan fmt.Println("num2=", num2) fmt.Printf("channel len= %v cap=%v ", len(intChan), cap(intChan)) // 2, 3 //6. 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock num3 := <-intChan num4 := <-intChan num5 := <-intChan fmt.Println("num3=", num3, "num4=", num4, "num5=", num5) }
应用实例
思路分析
代码实现
package main import ( "fmt" _ "time" ) //write Data func writeData(intChan chan int) { for i := 1; i <= 50; i++ { //放入数据 intChan<- i fmt.Println("writeData ", i) //time.Sleep(time.Second) } close(intChan) //关闭 } //read data func readData(intChan chan int, exitChan chan bool) { for { v, ok := <-intChan if !ok { break } //time.Sleep(time.Second) fmt.Printf("readData 读到数据=%v ", v) } //readData 读取完数据后,即任务完成 exitChan<- true close(exitChan) } func main() { //创建两个管道 intChan := make(chan int, 50) exitChan := make(chan bool, 1) go writeData(intChan) go readData(intChan, exitChan) //time.Sleep(time.Second * 10) for { _, ok := <-exitChan if !ok { break } } }
应用实例 3
代码实现
package main import ( "fmt" "time" ) //向 intChan 放入 1-8000 个数 func putNum(intChan chan int) { for i := 1; i <= 8000; i++ { intChan<- i } //关闭 intChan close(intChan) } // 从 intChan 取出数据,并判断是否为素数,如果是,就 // //放入到 primeChan func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) { //使用 for 循环 var flag bool for { time.Sleep(time.Millisecond * 10) num, ok := <-intChan if !ok { //intChan 取不到.. break } flag = true //假设是素数 //判断 num 是不是素数 for i := 2; i < num; i++ { if num % i == 0 {//说明该 num 不是素数 flag = false break } } if flag { //将这个数就放入到 primeChan primeChan<- num } } fmt.Println("有一个 primeNum 协程因为取不到数据,退出") //这里我们还不能关闭 primeChan //向 exitChan 写入 true exitChan<- true } func main() { intChan := make(chan int , 1000) primeChan := make(chan int, 2000)//放入结果 //标识退出的管道 exitChan := make(chan bool, 4) // 4 个 //开启一个协程,向 intChan 放入 1-8000 个数 go putNum(intChan) //开启 4 个协程,从 intChan 取出数据,并判断是否为素数,如果是,就 //放入到 primeChan for i := 0; i < 4; i++ { go primeNum(intChan, primeChan, exitChan) } //这里我们主线程,进行处理 //直接 go func(){ for i := 0; i < 4; i++ { <-exitChan } //当我们从 exitChan 取出了 4 个结果,就可以放心的关闭 prprimeChan close(primeChan) }() //遍历我们的 primeChan ,把结果取出 for { res, ok := <-primeChan if !ok{ break } //将结果输出 fmt.Printf("素数=%d ", res) } fmt.Println("main 线程退出") }
package main import ( "fmt" "time" ) func main() { //使用 select 可以解决从管道取数据的阻塞问题 //1.定义一个管道 10 个数据 int intChan := make(chan int, 10) for i := 0; i < 10; i++ { intChan<- i } //2.定义一个管道 5 个数据 string stringChan := make(chan string, 5) for i := 0; i < 5; i++ { stringChan <- "hello" + fmt.Sprintf("%d", i) } //传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock //问题,在实际开发中,可能我们不好确定什么关闭该管道. //可以使用 select 方式可以解决 //label: for { select { //注意: 这里,如果 intChan 一直没有关闭,不会一直阻塞而 deadlock //,会自动到下一个 case 匹配 case v := <-intChan : fmt.Printf("从 intChan 读取的数据%d ", v) time.Sleep(time.Second) case v := <-stringChan : fmt.Printf("从 stringChan 读取的数据%s ", v) time.Sleep(time.Second) default : fmt.Printf("都取不到了,不玩了, 程序员可以加入逻辑 ") time.Sleep(time.Second) return //break label } } }
package main import ( "fmt" "time" ) //函数 func sayHello() { for i := 0; i < 10; i++ { time.Sleep(time.Second) fmt.Println("hello,world") } } //函数 func test() { //这里我们可以使用 defer + recover defer func() { //捕获 test 抛出的 panic if err := recover(); err != nil { fmt.Println("test() 发生错误", err) } }() //定义了一个 map var myMap map[int]string myMap[0] = "golang" //error } func main() { go sayHello() go test() for i := 0; i < 10; i++ { fmt.Println("main() ok=", i) time.Sleep(time.Second) } }
以上是关于Golang-goroutine/channel的主要内容,如果未能解决你的问题,请参考以下文章