----------------------------------------- go 并发
// 注解:go 语言天生为程序并发所设计,可以说go的强项就是在cpu并发上的处理。 // go 语言层面就支持了并发。(不是一般高级语言的多线程并发,是系统级真实并发) // go 语言通过安全的通道发送和接受数据以实现同步 // 一般情况下,一个普通的桌面计算机跑十几二十几个线程就有点负载过大了,但是同样的硬件设备go可以轻松上K。
----------------------------------------- goroutine
// 注解:go 并发设计的核心,goroutine在并发中起到的作用就是协程(CSP),但是它比线程更小。(协程=微线程) // go 不支持后台协程,意思就是主程序退出,协程跟着一起退出 func newTask() { for { fmt.Println("new task ...") time.Sleep(time.Second) // 休眠1s } } func main() { go newTask() // 新建一个协程,新建一个任务 for { fmt.Println("main ...") time.Sleep(time.Second) // 休眠1s } } // result : // main... // new task ... // ....
----------------------------------------- gosched
// 注解:让出CPU时间片,让出当前 gorotine 的执行权限, // 调度器安排其他等待任务运行,并在下次某个时候从该位置恢复执行。 func main() { go func() { for i := 0; i < 5; i++ { fmt.Println("go") } }() for i := 0; i < 2; i++ { fmt.Println("hello") } // 这种情况,匿名函数未得到执行程序就结束了。 // 时间片案例演示代码,修改如下: for i := 0 .... { runtime.Gosched() fmt.Println("hello") } // 执行结果: // go go ... hello .. }
----------------------------------------- goexit
import "runtime" // 注解:终止所在的协程 (所在的协程不是当前函数) func test() { defer fmt.Println("ccccc") runtime.Goexit() fmt.Println("dddd") } func main() { go func (){ fmt.Println("aaaa") test() fmt.Println("bbbb") } for { } // result: // aaaa cccc }
----------------------------------------- gomaxProcs
// 注解:设置可以并行计算的 CPU 核数的最大值 import "runtime" func main() { n := runtime.GOMAXProcs(1) //制定以1核运算 fmt.Println("n = ", n) for { go fmt.Print(1) fmt.Print(0) } // 打印结果:11111.. 一大片, 00000...一大片 // 如果设置 GOMAXProcs(4) 为 4 核交叉效果更好 }
----------------------------------------- 资源争夺问题 channel
// 注解:channel 也是一种数据类型,同步 // 语法:channel <- value // 发送 value 数据到 channel // <- channel // 接收并丢弃 // 案例: // 全局变量,创建一个 channel var ch = make(chan int) // 定义一个打印机,参数为字符串,按每个字符打印 func Printer(str string) { for _, data := range str { fmt.Printf("%c", data) time.Sleep(time.Second) } fmt.Printf("/n") } func person1() { Printer("loong print") ch <- 666 // 给管道写数据 } func person2() { <- ch // 从管道取数据,如果管道没有数据前他就会阻塞 Printer("make print") } func main() { // 新建 2 个协程,代表 2 个人,2 个人同时使用打印机 go person1() go person2() } // 注解: 【认真看】 // 没有 channel 的情况 // 打印结果混乱,person1 打印一个h,person2 打印一个 w,交叉了。不符合我们的要求 // 增加 channel ,则在 <-ch 的地方进行了阻塞,通过进、出的方式融合这种解决这种并发互抢资源的问题。
----------------------------------------- channel 实现同步和数据交互
fun main() { ch := make(chan string) defer fmt.Println("主协程也结束") go func() { defer fmt.Println("子协程调用完毕。") for i := 0; i < 2; i++ { fmt.Println("子协程 i=", i) time.Sleep(time.Second) } ch <- "我是子协程,工作完毕" } str := <-ch // 没有数据前,阻塞 fmt.Println("str = ", str) } // 注意:程序需求:主程序结束之前,能够完整执行匿名函数中的代码 // 使用 channel 配合完成
----------------------------------------- channel 无缓存&有缓存
c1 := make(chan int) 无缓冲 c2 := make(chan int,1) 有缓冲 c1 < -1 // 无缓冲:不仅仅是向 c1 通道放 1, // 而是一直要等有别的协程 <-c1 接手了这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着。 // 有缓冲: c2<-1 则不会阻塞,因为缓冲大小是1(其实是缓冲大小为0), // 只有当放第二个值的时候,第一个还没被人拿走,这时候才会阻塞。 // 不需要再使用记得关闭channel close(c1) // 判断管道是否关闭 if num, ok := <- c1; ok == true { // 关闭了 } ----------------------------------------- channel 单方向 var ch1 chan int // ch1 是一个正常的 channle,不是单向的 var ch2 chan<- float64 // ch2 是单向 channel,只用于写 float64 数据 var ch3 <-chan int // ch3 是单向 channel,只用于读取 int 数据 // * 管道的操作,一定要避免死锁的情况。
----------------------------------------- channel 应用
// 此案例可以应用很多场景,每写一个,则可以消耗一个 // 此通道只能写,不能读 func producer(out chan<- int) { for i := 0; i < 10; i++ { out <- i * i } close(out) } // 此通道只能读,不能写 func consumer(in <-chan int) { for num := range in { fmt.Println("num = ", num) } } func main() { // 创建一个双向通道 ch := make(chan int) // 生产者,生产数字,写入 channel // 开启一个协程 go producer(ch) // 消费者,从channel读取内容打印 consumer(ch) }
----------------------------------------- Timer
import ( "time" "fmt" ) func main() { // 创建一个定时器,设置时间为2s,2s后,往time通道写内容 timer := time.NewTimer(2 * time.Second) fmt.Println("当前时间:", time.Now()) // 2s后,往timer.C写数据,有数据后读取 t := <-time.C // channel 没有数据前后阻塞 fmt.Println("t = ", t) }
----------------------------------------- select
// 注解:go语言提供了一个关键字 select,通过 select 可以监听 channel 上的数据流动 // 语法:(类似 switch) select { case <-chan: // 如果channel 成功读到数据,则进入 case 块语句 case chan<- 1: // 如果channel 成功写到数据,则进入 case 块语句 default: // 如果上面都没有成功,则进入default处理流程 // 注意:慎用,很消耗 cpu }