goroutine 协程
协程是goruntime
管理的轻量级线程, go f(args)
会启动一个goroutine并执行f(args)
, f和args的求值在当前的goroutine中, 而f的执行在新的goroutine中
package main
import (
"fmt"
"time"
)
func f(tag string) {
for i := 0; i < 3; i++ {
fmt.Println(tag, "-", i)
}
}
func main() {
f("main thread")
go f("goroutine")
go func() {
fmt.Println("takeing ...")
}()
time.Sleep(time.Second)
fmt.Println("finished")
}
//main thread - 0
//main thread - 1
//main thread - 2
//goroutine - 0
//takeing ...
//goroutine - 1
//goroutine - 2
//finished
可以看到2个协程是交替输出的, 表示go runtime是以并发的方式运行协程的, 等待协程的完成我们这里用的是sleep, 更好的方式是用WaitGroup.
信道
channels可以连接多个协程, 从一个协程将值发送到信道, 在另一个协程中接收, 使用make(chan val-type)
创建一个新通道, 类型就是需要传递值的类型, 使用channel <- val
把val发送到通道中, 使用<-channel
从通道中接收值, 箭头方向是数据流的方向;默认的发送和接收操作是阻塞的, 直到发送和接收方都就绪,这可以使goroutine在没有显式的锁和竟态变量的情况下同步.
package main
import "fmt"
func main() {
msgs := make(chan string)
go func() {
msgs <- "ping"
}()
msg := <-msgs
fmt.Println(msg)
}
信道缓冲
默认情况下, 信道是无缓冲的, 也就是说对应的接收方准备好接收时,才允许发送, 有缓冲的信道允许没有接收者时, 缓存一定数量的值.
package main
import "fmt"
func main() {
msgs := make(chan string, 2)
msgs <- "ping"
msgs <- "pong"
fmt.Println(<-msgs) //ping
fmt.Println(<-msgs) //pong
}
信道同步
利用信道阻塞的特性来通知协程的函数完成.
package main
import (
"fmt"
"time"
)
func ping(done chan bool) {
fmt.Println("ping")
time.Sleep(time.Second)
fmt.Println("pong")
done <- true
}
func main() {
done := make(chan bool)
go ping(done)
<-done // 如果没有接收, 程序不等协程执行就会直接退出了
}
信道方向
chan作为函数参数时, 可以指定信道是否为只读或者只写.
package main
import "fmt"
func ping(p chan<- string, msg string) {
p <- msg
}
func pong(p <-chan string, q chan<- string) {
msg := <-p
q <- msg
}
func main() {
p := make(chan string, 1)
q := make(chan string, 1)
ping(p, "~~~~~~")
pong(p, q)
fmt.Println(<-q)
}
range和close
发送者可以通过close
关闭信道, 接收者可以用v,ok:=<-ch
测试信道是否关闭, 如果关闭,ok为false.
只有发送者才能关闭信道, 向关闭的信道发送数据会引发panic
信道通常情况下不需要关闭, 只有在必须告诉接收者是否还需要接收时才有必要关闭,比如终止循环.
package main
import "fmt"
func fib(n int, c chan int) {
a, b := 0, 1
for i := 0; i < n; i++ {
c <- a
a, b = b, a+b
}
close(c)
}
func main() {
c := make(chan int, 10)
go fib(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
信道选择器
select选择器可以让你同时等待多个信道操作.
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("revc:", msg1)
case msg2 := <-c2:
fmt.Println("revc:", msg2)
}
}
fmt.Println("use time:", time.Now().Sub(start))
}
//revc: one
//revc: two
//use time: 2.00109499s
如预期一样, 先收到one,后收到two
超时处理
<-time.After()
等待超时几秒, 我们等待时间长一点就成功接收到c2的值了,select
会阻塞到某个分支可以继续执行为止,这时就会执行该分支;当多个分支都准备好时会随机选择一个执行.
package main
import (
"fmt"
"time"
)
func main() {
c1, c2 := make(chan string, 1), make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c1 <- "1"
}()
select {
case r1 := <-c1:
fmt.Println(r1)
case <-time.After(time.Second):
fmt.Println("timeout 1")
}
go func() {
time.Sleep(2 * time.Second)
c2 <- "2"
}()
select {
case r2 := <-c2:
fmt.Println(r2)
case <-time.After(3 * time.Second):
fmt.Println("timeout 2")
}
}
//timeout 1
//2
互斥锁
如果在并发中不需要通信, 只想保证每次只要一个协程能访问一个共享变量, 就需要用互斥锁来提供这种机制, sync.Mutex互斥锁提供了Lock和UnLock两个方法.
package main
import (
"fmt"
"sync"
"time"
)
type Counter struct {
c map[string]int
mu sync.Mutex
}
func (co *Counter) inc(name string) {
co.mu.Lock()
defer co.mu.Unlock() // 函数结束时解锁
co.c[name]++
}
func main() {
co := Counter{
c: map[string]int{"test": 0},
}
for i := 0; i < 1000; i++ {
go co.inc("test")
}
time.Sleep(time.Second)
fmt.Println(co.c["test"])
}
学习更多Golang知识
不负春光 不负自己
戳“更多文章”我们一起进步