终于来到了go最强悍的地方语言层面支持并发,这是一种类似于协程的机制, gorutine是一个非常轻量级的实现,一般情况下,单进程可以实现千万级的并发任务。先来搞一下基本概念:
1.注意并发跟并行的区别:
- 并发性(concurrency),又称共行性,是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。
- 并行(parallelism)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行
举个形象的例子就是并发就是拉链式车道,就是三车道在某个路口合并到单车道,那个车道有车,就依次并入单车道,而并行就是三车道,想要行驶,您随意,毕竟是人民币玩家。
想要更牛的效率,那就需要高配,推荐苹果垃圾桶,28核,30+w软妹币,但一般而言,我们遇到大多情况都是io操作,没有那么多计算,所以go的并发真真正正实现低配电脑也能高效,这也就是go的魅力所在吧,一般情况下,go默认的进程启动后一个系统线程服务于goroutine,但是如果是人民币玩家,可以使用 runtime.GOMAXPROCS 修改,让调度器用多个线程实现多核并行。
2.goroutine之间的内存不是共享的,但为了实现goroutine之间的通信,可以使用channel,实现“以通讯来共享内存”
3.gorutine的执行顺序不是一定的,并且当前进程推出时,不会等你起的那些gorutine结束,
这个就是简单起了10个gorutine,终端打印结果是不一定的,跟电脑状态有关,简单列了三次打印情况,我这个电脑比较垃圾,是MacAir,攒钱换垃圾桶
func tell(i int){ fmt.Println("i am ", i) } func main(){ fmt.Println("start") for i := 0; i < 10; i++{ go tell(i) } fmt.Println("end") }
1: start i am 1 i am 0 end i am 2 2: start end i am 5 i am 9 3: start end
这时就有一个疑问,我们想要所有的gorutine执行完毕,在结束程序怎么做,这里有几种方法
可以使用sync包中的WaitGroup,它能够一直等所有的groutine执行完毕,在此之前会一直阻塞主进程,ADD是告知增加了几个gorutine,Done是告知一个gorutine完结,相当于Add(-1),
Wait会一直阻塞,直到这里面的值为空
func tell(i int) { fmt.Println("i am ", i) } func main() { var wg sync.WaitGroup fmt.Println("start") for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() tell(i) }(i) } wg.Wait() fmt.Println("end") } //start //i am 4 //i am 5 //i am 9 //i am 7 //i am 8 //i am 6 //i am 2 //i am 1 //i am 0 //i am 3 //end
另外对于终结当前的gorutine,可以使用runtime.Goexit(),这个会直接推出当前的gorutine,调度器会确保所有已经注册的defer会执行,其余的不执行
func main() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() defer println("A.defer") // 执行 func() { defer println("B.defer") // 执行 runtime.Goexit() defer println("B.after defer") println("B") }() println("A") }() wg.Wait() }
goroutine有太多作用啦,比如你要送发消息,邮件,短信,等等,可以直接起个gorutine,然后返回,高效。
下面来讲讲与之配套的channel
channnel默认是同步模式,需要发送与接收配对,否则就会被阻塞,直到另一方准备好,就是消费者生产者模型,默认开启的chan缓存区就一个,因此在往里面存放数据的时候,如果没有取走,会阻塞住,因此close(msg)会等待存完,取完后在执行,因此可以这样理解,缓冲区已满,则发送会被阻塞,如果缓存区为空,则接受会被阻塞
func main() {
msg := make(chan int)
flag := make(chan bool)
go func(){
fmt.Println("开始接受啦")
for i := range msg{
fmt.Println(i)
}
flag <- true
}()
for i := 0; i < 10; i++{
msg <- i
}
close(msg)
fmt.Println("关闭msg通道")
fmt.Println(<-flag)
}
}
//
开始接受啦
0
1
2
3
4
5
6
7
8
9
关闭msg通道
true
比如,我开启的channel缓存区比较大,那么存放数据不会被阻塞,就会直接执行,那为什么主程序没有直接关闭呢,那是因为flag这个channel阻塞了,他会一直等到gorutine往里面存入数据,才可以取出来,如果没有flag这个channel,程序会直接推出,也就看不到起的gorutine打印结果啦。
func main() { msg := make(chan int, 10) flag := make(chan bool) go func() { fmt.Println("开始接受啦") for i := range msg { fmt.Println(i) } flag <- true }() for i := 0; i < 10; i++ { msg <- i } close(msg) fmt.Println("关闭msg通道") fmt.Println(<-flag) } // 关闭msg通道 开始接受啦 0 1 2 3 4 5 6 7 8 9 true
如果向已关闭的channel发送数据,则会报错,陪panic捕捉,如果channel关闭两次,也会报错
func main() { msg1 := make(chan int) close(msg1) //close(msg1) // 关闭两次,panic: close of closed channel fmt.Println(<-msg1) // 已关闭取出的值是空值,int空值为 0 //msg1 <- 2 // panic: send on closed channel i, ok := <-msg1 // 可以检查channel是否已关闭 if ok { fmt.Println(i) } else { fmt.Println("msg1 closed") // msg1 closed } }
单向channel,可以将channel设置为只接受,或者只发送,send为只能发送的,如果从里面取数据,就会报错这个需要指定缓冲区,否则会报错,反之亦然,例如
func main() { m := make(chan int, 1) var send chan <- int = m var recv <-chan int =m send <- 1 //<- send // invalid operation: <-send (receive from send-only type chan<- int) //recv <- 1 // invalid operation: recv <- 1 (send to receive-only type <-chan int) println(<- recv) // 1 }
channel结合select也可以设置超时
func main() { w := make(chan bool) c := make(chan int, 2) go func() { select { // 如果两个case都不满足,会一直等到满足 case v := <-c: fmt.Println(v) case <-time.After(time.Second * 3): fmt.Println("timeout.") } w <- true }() <-w // 阻塞,等待程序结束 }