本文主要是介绍无缓冲阻塞 chan 杂谈,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
chan类似队列版管道,无缓冲chan看起来好像是全局变量,通过它可让多个goroutine间通信。 这其实隐含一个事实,chan阻塞会引发goroutine上下文切换,而切换到哪一个可执行goroutine由go调度器决定(与阻塞chan相关)。go当前能够使用的goroutine,必须在其待命队列中,否则会产生死锁。
上下文切换
多进程多线程都具备上下文切换,即保存恢复现场的能力。goroutine的上下文切换实现,是在用户态基础上进行,只不过它涉及到的资源比线程更少,如产生一个线程系统调用分配内存通常在1M,而goroutine只有2kb,此外在使用寄存器,段位上,goroutine也只需3个左右,而线程则通常在10个左右。
无缓冲阻塞
go调度器对goroutine的使用配合chan,具有有序性(在高并发访问对象时,可用chan这种特性让访问请求隐性排队,解决竞态问题)。main函数是特殊的入口goroutine,若有阻塞代码,运行时runtime会寻找已入队列的goroutine并在适当的时机调用它。chan并不是全局变量,确切来说它的读/写阻塞会触发当前goroutine执行权转移,它只是个通信器。好似打电话,必须先知道对方号码并有连线,才能正常工作,若顺序不对,表现在golang中便是死锁
Blocking
package mainimport ("fmt"
)func f1(in chan int) {fmt.Println(<-in)
}func main() {out := make(chan int)out <- 2go f1(out)
}
上述代码会产生死锁,main入口goroutine,通道out产生了发送阻塞,此时runtime会尝试调度与out通道读相关的goroutine执行,但可惜的是,在 out <- 2
之前,并没有向go执行器队列加入与out读相关的goroutine。换句话而言,f1压根就没入队,没有执行机会。
unblocking
package mainimport "fmt"func main() {out := make(chan int)go f1(out)// 此处顺序大有讲究,在使用发送通道之前必需想好数据接收的退路,f1即是out <- 2}func f1(in chan int) {fmt.Println(<-in)}
chan vs 全局变量
上文提到chan类似管道,管道顾名思义一端进一端出,很形象表明了一个连接器。go中的chan连接goroutine,游离于众多goroutine之间,功用性与全局变量有得一拼。但chan绝对不是全局变量,一个全局变量,可以在同一函数体内重复读写,但对无缓冲chan而言是不可以,原因在同一goroutine内对同一chan读写时,存在读或写阻塞面临切换上下文,另一个对应的永远没执行机会,如下
- 无缓冲通道死锁
package mainimport "fmt"func main() {ch := make(chan int)ch <- 5fmt.Println(<-ch)}
- 有缓冲通道正常
package mainimport "fmt"func main() {ch := make(chan int, 1)ch <- 5fmt.Println(<-ch)}
有缓冲通道,意味着在未超过当前通道限制数之前,当前的goroutine是非阻塞,不会发生上下文切换,即当前goroutine的控制权不发生转移,runtime也就不会去寻求其它相关goroutine执行。
小结
- 无缓冲chan 进和出都会阻塞.
- 有缓冲chan 先进先出队列, 出会一直阻塞到有数据, 进时当队列未满不会阻塞, 队列已满则阻塞.
这篇关于无缓冲阻塞 chan 杂谈的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!