简介
协程是golang的一大特色和卖点. 协程(goroutine) 是轻量级的执行线程,应用go
关键字到函数或者lamba表达式能够疾速启动协程.协程函数的返回值会被摈弃.线程的调度由操作系统来治理,是抢占式调度。而协程不同,协程须要互相配合,被动交出执行权。
配置
GOMAXPROCS 设置逻辑CPU数量,个别状况下应用CPU外围数量值.应用足够的线程来进步golang的并行执行效率.如果你的业务是IO密集型则能够设置数倍与CPU外围数的值来失去更好的性能.如果Go程序在容器中执行则须要依据状况缩小该值得,因为容器中无奈应用到宿主机的全副外围.设置更小的值能够防止线程切换的开销.
应用channel进行协程通信
定义通道
ch1 := make(chan string) //定义了一个无缓冲的string通道ch2 := make(chan string , 4) //定义了一个4个元素的string通道
通道操作符
ch1 <- "Chengdu" //向通道写入数据itemOfCh1 := <- ch1 //从ch1通道读取一条数据<- ch1 //读取通道的下一个值var in_only chan<- int //只能接管通道var out_only <-chan int //只读取的通道close(ch) //敞开通道
通道阻塞
默认状况通道是同步无缓冲的,在接受方未筹备好之前发送方是阻塞的.通道中没有数据则接管方也是阻塞的.
package mainimport ( "fmt" "time")func f1(in chan int) { data := <-in fmt.Println(data)}func main() { out := make(chan int) out <- 2 fmt.Println(v) go f1(out) time.Sleep(100 * time.Millisecond)}
以上程序会panic退出,因为out写入数据并没有接受者,因而main主协程被阻塞了.前面的代码永远不会被执行,因而通道永远不会有数据,产生了死锁.批改 out:=make(chan int , 1)
让通道有一个缓冲则不会死锁.或者在写入前启动读取的协程.或者在另外一个协程来读取都能够解决这个问题.
应用信号量
能够通过信号量来让主协程期待子协程的实现退出执行.
package mainimport ( "fmt" "time")func f1(in chan int, done chan int) { data := <-in fmt.Println(data) time.Sleep(10e9) done <- 1}func main() { out := make(chan int) done := make(chan int) go f1(out, done) out <- 2 <-done}
输入 2 之后10秒后程序才会退出,咱们就不须要应用sleep来让主过程执行.
敞开通道
显式的敞开通道,敞开通道示意发送者不会有新的数据发送给接受者了.只有发送者须要敞开通道.
ch := make(chan int )defer close(ch)data,ok := <-ch //接管到数据则ok为 true,应用ok能够检测通道是否敞开或者阻塞
上面这种状况,读取通道在主过程不会报死锁谬误,因为查看到通道敞开后就不进行通道读取跳出循环,因而不会再继读没有写入的通道.所以没有死锁.
package mainimport "fmt"func makeStream(n int) chan bool { ch := make(chan bool, n) go func() { for i := 0; i < n; i++ { ch <- true } close(ch) }() return ch}func main() { stream := makeStream(5) for { v, ok := <-stream if !ok { break } fmt.Println(v) }}
应用select 切换协程
从不同并发协程获取值能够用select
关键字来进行轮训.通常和for
循环一起应用
- 如果都阻塞了期待其中一个能够解决
- 如果多个能够解决随机抉择一个.外层有循环则下次再解决残余的
- 如果没有通道能够解决而有
default
则执行default.否则始终阻塞 - 如果没有case,这select会始终阻塞
- 能够应用break跳出select
package mainimport ( "fmt" "time")func main() { ch1 := make(chan string) ch2 := make(chan string) go func() { for i := 0; i < 10; i++ { ch1 <- fmt.Sprintf("A%d", i) } }() go func() { for i := 0; i < 10; i++ { ch2 <- fmt.Sprintf("B%d", i) } }() go func() { for { select { case v := <-ch1: fmt.Println(v) case v := <-ch2: fmt.Println(v) } } }() time.Sleep(1e9)}
能够应用这种模式做为服务端来循环解决客户申请
计时器(Ticker)
type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. r runtimeTimer}
定时器的C变量会依据你创立的定时器工夫,在给定工夫外向该通道写入工夫
package mainimport ( "fmt" "time")func main() { t := time.NewTicker(time.Second) go func() { for { v := <-t.C fmt.Println(v) } }() time.Sleep(10e9) // <-time.After(10e9) 应用通道来设置超时}
应用
time.Tick(duration)
能够间接获取通道,相似time.NewTicker(1e9).C
time.After(duration)
只发送一次工夫.能够应用这个通道来解决超时
协程的复原
协程在遭逢panic
时平安退出,而不影响其余协程
package mainimport ( "log" "time")func doWork() { time.Sleep(4e9) panic("fk")}func main() { go func() { for { log.Printf("another worker") time.Sleep(1e9) } }() go func() { defer func() { if err := recover(); err != nil { log.Printf("出问题了 %s", err) } }() doWork() }() time.Sleep(10e9)}
应用锁还是通道
在一种场景下,有多个工作,一个worker解决一项工作.这种场景很适宜应用通道和协程来解决问题
package maintype Task struct{}type Result struct{}func process(Task *Task) Result { return Result{}}func main() { tasks, results := make(chan Task), make(chan Result) workCount := 10 //创立工作 go func() { for i := 0; i < workCount; i++ { tasks <- Task{} } }() //启动worker for i := 0; i < workCount; i++ { go func() { for { t := <-tasks result := process(&t) //解决数据 results <- result //写入构造 } }() } //生产后果}
应用锁的情景:
- 访问共享数据结构中的缓存信息
- 保留应用程序上下文和状态信息数据
应用通道的情景:
- 与异步操作的后果进行交互
- 散发工作
- 传递数据所有权