Goruntines
在 go 语言中,每一个并发的执行单元叫作一个 goroutine 。
- goroutin 生成:
一 个 go 生成一个协程
当一个程序启动时,其主函数即在一个单独的 goroutine 中运行,我们叫它 main goroutine 。新的 goroutine 会用 go 语句来创建。
- goroutin 结束:
每一个 goroutine 都是独立的。新的 gorountine 进行过程中, main goroutine 也会继续执行,当 main goroutine 执行结束后,所有的 goroutine 都结束 。
主函数返回时, 所有的 goroutine 都会被直接打断, 程序退出。除了从主函数退出或者直接终止程序之外, 没有其他的编程方法能够让一个 goroutine 来打断另一个执行, 但是之后可以看到一种方式来实现这个目的,通过 goroutine 之间的通信来让一个 goroutine 请求其他的 goroutine,并让被请求的 goroutine 自行结束执行。
//启了一个 goroutine 去执行 spinner(), 但 main goroutine 还在执行 //故 main goroutine 执行完最后一句 fmt.Printf() 后,子协程也需要结束,尽管还在 sleep()。 func main() {
go spinner(100 * time.Millisecond) const n = 45 fibN := fib(n) // slow fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) } func spinner(delay time.Duration) {
for {
for _, r := range `-\|/` {
fmt.Printf("\r%c", r) time.Sleep(delay) } } } func fib(x int) int {
if x < 2 {
return x } return fib(x-1) + fib(x-2) }
讯享网
Channels
- channel 介绍:
使用内置的 make 函数, 我们可以创建一个 channel:
讯享网ch := make(chan int) // ch has type 'chan int',不带缓存 ch := make(chan int, 3) // ch has type 'chan int',带缓存
- channel使用—本质上是使用引用
和map类似,channel也对应一个make创建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。
- channel 运算符
- ==
两个相同类型的channel可以使用 == 运算符比较 - <-
一个channel有发送和接受两个主要操作,都是通信行为。一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine。发送和接收两个操作都使用<-运算符。
ch <- x // 发送语句 x = <-ch // 接受语句 <-ch // 接受语句,也合法
- close() 关闭channel
讯享网close(ch)
不带缓存的Channels
基于无缓存 Channels 的发送和接收操作将导致两个 goroutine 做一次同步操作。
一个基于无缓存 Channels 的发送操作将导致发送者 goroutine 阻塞,直到另一个 goroutine 在相同的 Channels 上执行接收操作,当发送的值通过 Channels 成功传输之后,两个 goroutine 可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者 goroutine 也将阻塞,直到有另一个 goroutine 在相同的 Channels 上执行发送操作。
串联的Channels ( Pipeline )
Channels 也可以用于将多个 goroutine 连接在一起,一个 Channel 的输出作为下一个 Channel 的输入。这种串联的 Channels 就是所谓的管道( pipeline )。下面的程序用两个 channels 将三个 goroutine 串联起来

第一个goroutine是一个计数器,用于生成0、1、2、……形式的整数序列,然后通过channel将该整数序列发送给第二个goroutine;
第二个goroutine是一个求平方的程序,对收到的每个整数求平方,然后将平方后的结果通过第二个channel发送给第三个goroutine;
第三个goroutine是一个打印程序,打印收到的每个整数。
为了保持例子清晰,我们有意选择了非常简单的函数,当然三个goroutine的计算很简单,在现实中确实没有必要为如此简单的运算构建三个goroutine。
func main() {
naturals := make(chan int) squares := make(chan int) // Counter go func() {
for x := 0; x < 100; x++ {
naturals <- x } close(naturals) }() // Squarer go func() {
for x := range naturals {
squares <- x * x } close(squares) }() // Printer (in main goroutine) for x := range squares {
fmt.Println(x) } }
单方向的Channel
随着程序的增长,人们习惯于将大的函数拆分为小的函数。
讯享网func counter(out chan int) func squarer(out, in chan int) func printer(in chan int)
为了表明这种意图并防止被滥用,Go语言的类型系统提供了单方向的 channel 类型,分别用于只发送或只接收的 channel 。类型 chan<- int 表示一个只发送 int 的 channel ,只能发送不能接收。相反,类型 <-chan int 表示一个只接收 int 的 channel ,只能接收不能发送。(箭头<-和关键字 chan 的相对位置表明了 channel 的方向。)这种限制将在编译期检测。
func counter(out chan<- int) {
for x := 0; x < 100; x++ {
out <- x } close(out) } func squarer(out chan<- int, in <-chan int) {
for v := range in {
out <- v * v } close(out) } func printer(in <-chan int) {
for v := range in {
fmt.Println(v) } } func main() {
naturals := make(chan int) squares := make(chan int) go counter(naturals) go squarer(squares, naturals) printer(squares) }
备注:调用 counter(naturals)时,naturals 的类型将隐式地从 chan int 转换成 chan<- int 。调用 printer(squares) 也会导致相似的隐式转换,这一次是转换为 <-chan int 类型只接收型的 channel 。任何双向 channel 向单向 channel 变量的赋值操作都将导致该隐式转换。这里并没有反向转换的语法:也就是不能将一个类似 chan<- int 类型的单向型的 channel 转换为 chan int 类型的双向型的 channel 。
带缓存的Channels
带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。
讯享网ch = make(chan string, 3)
向缓存 Channel 的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个 goroutine 执行接收操作而释放了新的队列空间。相反,如果 channel 是空的,接收操作将阻塞直到有另一个 goroutine 执行发送操作而向队列插入元素。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/11932.html