在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用的函数返回时,这个goroutine也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
func main() {
fmt.Println("In main()")
go longWait()
go shortWait()
fmt.Println("About to sleep in main()")
//time.Sleep(4 * 1e9) 在longWait()结束之前,main已经退出,也就看不到输出
fmt.Println("At the end of main()")
}
func longWait() {
fmt.Println("Beginning longWait()")
time.Sleep(5 * 1e9)
fmt.Println("End of longWait()")
}
func shortWait() {
fmt.Println("Beginning shortWait()")
time.Sleep(2 * 1e9)
fmt.Println("End of shortWait()")
}
main()启动2个goroutine后返回,这时程序就退出了,而被启动的执行goroutine 没来得及执行。我们想要让 main()等待所有 goroutine 退出后再返回:
- time.Sleep(5*time.Second)
- sync.WaitGroup
package main
import "sync"
var wg sync.WaitGroup
func say(s string) {
for i := 0; i < 5; i++ {
println(s)
}
wg.Done() // 相当于 wg.Add(-1) 表示这个协程执行完了
}
func main() {
wg.Add(2) // 有2个goroutine要执行
go say("Hello")
go say("World")
wg.Wait() // 告诉主线程等一下,等他们2个都执行完再退出。
}
// 定义wg时用指针
func TestGoroutineSync(){
wg := &sync.WaitGroup{}
wg.Add(2)
go func(wg *sync.WaitGroup){
fmt.Println("g1")
wg.Done()
}(wg)
go func(wg *sync.WaitGroup){
fmt.Println("g2")
wg.Done()
}(wg)
wg.Wait()
fmt.Println("before exit")
}
通道(channel)
协程都是独立运行的,他们之间没有通信。channel用于两个goroutine之间,通过传递一个指定类型的值来同步运行和通讯
ch := make(chan int) // 默认不带缓冲区
ch <- v // 把 v 发送到通道 ch
v := <- ch // 从 ch 接收数据,并把值赋给 v
ch := make(chan <-int) // 单向,只可写
ch := make(<-chan int) // 单向,只可读
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态:
ch := make(chan int, 100) // 缓冲区大小100,在buf满之前,通道都不会阻塞。
for v, ok := <- ch { } //遍历通道
channel用法
操作 | 值为 nil 的 channel | 通道关闭 | 正常的 channel |
---|---|---|---|
close | panic | panic | 成功关闭 |
写c<- | 永远阻塞 | panic | 阻塞或成功发送 |
读<-c | 永远阻塞 | 永远不阻塞 | 阻塞或成功接收 |
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收值。 如果通道带缓冲,发送方则会阻塞直到发送值被拷贝到缓冲区内;如果缓冲区已满,则要等待直到某个接收方获取到一个值。接收方在有值可接收之前会一直阻塞。
//生产者和消费者模式[无缓冲区时]:堵塞
package main
import "fmt"
import "sync"
func main() {
ch1 := make(chan int)
go pump(ch1)
fmt.Println(<-ch1) // 被阻塞直到有数据接收
}
func pump(ch chan int) {
for i := 0; ; i++ {
ch <- i // 阻塞直至main被读出
}
}
//生产者和消费者模式1
func main() {
ch1 := make(chan int)
go pump(ch1)
go suck(ch1)
time.Sleep(1e9)
}
func pump(ch chan int) {
for i := 0; ; i++ {
ch <- i
}
}
func suck(ch chan int) {
for {
fmt.Println(<-ch)
}
}
//生产者和消费者模式2:
var wg sync.WaitGroup
func say(s string, c chan string) {
defer wg.Done()
for i := 0; i < 5; i++ {
c <- s
}
}
func main() {
wg.Add(2)
ch := make(chan string) // 实例化一个管道
go say("Hello", ch)
go say("World", ch)
for {
println(<-ch) // 循环从管道取数据
}
close(ch) // 协程可能会panic??
wg.Wait()
}
// ----------------------------------
// 无缓冲区:
func main() {
chan1 := make(chan int)
go func() {
for d := range chan1 {
fmt.Println(d)
}
}()
chan1 <- 1 //发送要放在接收协程跑起来后面,因为发送后会阻塞等待接收
chan1 <- 2
close(chan1)
}
// 有缓冲区:控制主进程的退出
var ichan = make(chan int, 3)
var str string
func f() {
str = "hello world"
ichan <- 0
}
func main() {
go f()
<-ichan //这里有值,下面才会运行
fmt.Println(str)
}
//
func main() {
chan1 := make(chan int, 3)
quit := make(chan bool) // 阻塞主进程,防止未处理完的子协程
go func() {
for d := range chan1 { //如果缓冲区为空,协程会一直阻塞,除非被channel被close
fmt.Println(d)
}
quit <- true
}()
chan1 <- 1
chan1 <- 2
chan1 <- 3
close(chan1) // 用完要关闭否则goroutine会被死锁。因为上面用range,信道关闭是不会结束读取的.
<-quit // 等待协程执行完后,解除阻塞
}
for range 来读取channel数据
func main() {
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c) //如果close(c)注释掉,程序会一直阻塞在for...range那一行
}()
for i := range c {
fmt.Println(i)
}
fmt.Println("end!")
}
select: channel + time.After + time.NewTicker()
多个case情况下,如果没有default也没有case需要处理的,select会阻塞直到某个case需要处理。
func foo(i int) chan int {
c := make(chan int)
go func() {
c <- i
}()
return c
}
func main() {
c1, c2, c3 := foo(1), foo(2), foo(3)
ichan := make(chan int)
//开一个goroutine监听各个channel数据输出并收集数据到channel
go func() {
for { //for语句循环处理select, 如果只有select,它只会选一个case处理就结束了
select { //监听c1,c2,c3流出,并全部流入到ichan
case v1 := <-c1:
ichan <- v1
case v2 := <-c2:
ichan <- v2
case v3 := <-c3:
ichan <- v3
}
}
}()
//阻塞主协程,取出ichan的数据
for i := 0; i < 3; i++ {
fmt.Println(<-ichan) // 从打印来看我们的数据输出并不是严格的1,2,3顺序
}
fmt.Println("end!")
}
// select超时处理协程
func main() {
chan1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 3)
chan1 <- "res1"
}()
select {
case res := <-chan1: //3秒之后才会有数据进入槽chan1
fmt.Println(res)
case <-time.After(time.Second * 1): //定义超时情况,1秒后超时.这个超时时间比上面的case短,所以先运行这个case
fmt.Println("timeout 1")
}
}
匿名函数:go 函数名(参数列表) + close(ch) 防止阻塞
通道死锁
通道两段互相阻塞对方,会形成死锁状态。Go运行时会检查并panic,停止程序。无缓冲通道会被阻塞。
package main
import "fmt"
func main() {
out := make(chan int)
out <- 2 //由于没有接受者,主线程一直被被阻塞!
go f1(out)
}
func f1(in chan int) {
fmt.Println(<-in)
}
// fatal error: all goroutines are asleep - deadlock!
- 信道默认堵塞,此时可能死锁? 主动关闭管道![通过close(ch) 通知多个goroutine,是因为close后,ch是一直可读的且读到内容是nil]
- 尽量在首要位置使用无缓冲通道,只在不确定的情况下使用缓冲。
- 在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic
参考:https://juejin.im/post/5c6a507fe51d45086925e22c
context底层原理:
ctx, cancel := context.WithCancel(context.Background())
select默认阻塞,随机选择一个执行的,可设置超时。
runtime包中有几个处理goroutine的函数:
- Goexit: 退出当前执行的goroutine,但是defer函数还会继续调用
- Gosched: 让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
- NumCPU: 返回 CPU 核数量
- NumGoroutine: 返回正在执行和排队的任务总数
- GOMAXPROCS: 用来设置可以并行计算的CPU核数的最大值,并返回之前的值。