1 golang协程(goroutine)
1.1 goroutine使用
golang在语言层面原生支持协程,在方法或函数前面加上关键字go,就会运行一个新的goroutine
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
注意:我让main函数sleep了1s钟。如果不加sleep,上述程序什么也不会输出,因为main函数先于协程结束了。这里就引出了一个问题:怎样让主线程等待所有的协程结束后再结束?2.4 ,2.5 ,2.6会讲到。请继续往下看
1.2 goroutine一些注意点
a.子协程异常挂掉,整个进程就会挂掉
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("run in main goroutine")
go func() {
fmt.Println("run in child goroutine")
var ptr *int
*ptr = 0x12345 // 故意制造崩溃
}()
time.Sleep(time.Second)
fmt.Println("main goroutine will quit")
}
打印:
可以看到,协程崩溃后的打印信息没有输出
b.使用recover处理异常
recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("run in main goroutine")
go func() {
defer func() { // 要在对应的协程里执行
fmt.Println("执行defer:")
if err := recover(); err != nil {
fmt.Println("捕获error:", err)
}
}()
var ptr *int
*ptr = 0x12345 // 故意制造崩溃
fmt.Println("run in child goroutine")
}()
time.Sleep(time.Second)
fmt.Println("main goroutine will quit")
}
2 golang通道(channel)
go语言的并发机制是使用协程,channel就是协程间的通信机制。通道是一个容器,它可以向容纳协程写入的数据,也可以向协程提供数据,即协程对它可读可写;协程是有大小的,需要注意满或空;通道也是有类型的,它可以限定存放的数据类型
2.1 创建channel
channel分为两种:缓冲型通道和非缓冲型通道,都使用make创建,只此一种方法
缓冲型:var bufChannel = make(chan int, 10)
非缓冲型:var nonebufChannel = make(chan int)
2.2 读写channel
使用go提供的语法<-,通道在箭头左边:写通道;通道在箭头右边:读通道
ch <- value //写通道
calue <- ch //读通道
2.3 读写阻塞
通道为空,读操作阻塞,通道满了,写操作阻塞
2.4 WaitGroup --实现主线程等待所有协程结束再退出
sync.WaitGroup是等待一组协程结束,sync.WaitGroup只有3个方法,Add()添加一个计数,Done()减去一个计数,Wait()阻塞直到所有任务完成。
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup //定义一个同步等待的组
func task(i int) {
fmt.Println("task...", i)
//耗时操作任务,网络请求,读取文件
time.Sleep(time.Second)
wg.Done() //减去一个计数
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) //添加一个计数
go task(i)
}
wg.Wait() //阻塞直到所有任务完成
fmt.Println("over")
}
2.5 缓冲通道
-- 利用缓冲通道等待所有协程结束 --无序
package main
import (
"fmt"
)
var ch = make(chan int, 10)
func task(i int) {
fmt.Println("task...", i)
ch <- i
}
func main() {
for i := 0; i < 10; i++ {
go task(i)
}
for i := 0; i < 10; i++ {
<-ch
}
fmt.Println("over")
}
2.6 无缓冲通道
--利用无缓冲通道等待所有协程结束 --有序
package main
import (
"fmt"
"time"
)
var ch = make(chan int)
func task(i int) {
fmt.Println("task...", i)
time.Sleep(time.Second)
<-ch
}
func main() {
for i := 0; i < 10; i++ {
go task(i)
ch <- i
}
fmt.Println("over")
}
2.7 select
select语句用于在多个发送/接收信道操作中进行选择。select语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select会随机地选取其中之一执行。
package main
import (
"fmt"
"time"
)
func send(ch chan int, gap time.Duration) {
i := 0
for {
i++
ch <- i
time.Sleep(gap)
}
}
func recv(ch1 chan int, ch2 chan int) {
for {
select {
case v := <-ch1:
fmt.Printf("recv %d from ch1\n", v)
case v := <-ch2:
fmt.Printf("recv %d from ch2\n", v)
}
}
}
func main() {
var ch1 = make(chan int)
var ch2 = make(chan int)
go send(ch1, time.Second)
go send(ch2, 2*time.Second)
recv(ch1, ch2)
}
2.8 非阻塞读写
2.7的写法只有当通道有内容时才会消费,通道为空时阻塞。如果我们加上default分支,这样当通道为空时就会执行default分支,起到了非阻塞读写的效果
package main
import (
"fmt"
"time"
)
func send(ch1 chan int, ch2 chan int) {
i := 0
for {
i++
select {
case ch1 <- i:
fmt.Printf("send ch1 %d\n", i)
case ch2 <- i:
fmt.Printf("send ch2 %d\n", i)
default:
fmt.Printf("ch block\n")
time.Sleep(2 * time.Second) // 这里只是为了演示
}
}
}
func recv(ch chan int, gap time.Duration, name string) {
for v := range ch {
fmt.Printf("receive %s %d\n", name, v)
time.Sleep(gap)
}
}
func main() {
// 无缓冲通道
var ch1 = make(chan int)
var ch2 = make(chan int)
// 两个消费者的休眠时间不一样,名称不一样
go recv(ch1, time.Second, "ch1")
go recv(ch2, 2*time.Second, "ch2")
send(ch1, ch2)
}