在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用的函数返回时,这个goroutine也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
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个都执行完再退出。
}
通道(channel)
用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯
ch := make(chan int) // 默认不带缓冲区
ch <- v // 把 v 发送到通道 ch
v := <- ch // 从 ch 接收数据,并把值赋给 v
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态:
ch := make(chan int, 100) // 缓冲区大小100,在buf满之前,通道都不会阻塞。
for v, ok := <- ch { } //遍历通道
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收值。 如果通道带缓冲,发送方则会阻塞直到发送值被拷贝到缓冲区内;如果缓冲区已满,则要等待直到某个接收方获取到一个值。接收方在有值可接收之前会一直阻塞。
//生产者和消费者模式:堵塞
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) // 循环从管道取数据
}
wg.Wait()
}
匿名函数: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!
- 信道默认堵塞,此时可能死锁? 主动关闭管道!
- 尽量在首要位置使用无缓冲通道,只在不确定的情况下使用缓冲。