【golang学习】goruntines和channels

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 介绍:

channels 是 goroutine 之间的通信机制。
一个 channel 是 一个通信机制,它可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。每个 channel 都有一个特殊的类型, 也就是 channel 可发送的数据。 一个可以发送 int 类型数据的 channel 一般写为 chan int。

使用内置的 make 函数, 我们可以创建一个 channel:

ch := make(chan int) // ch has type 'chan int',不带缓存
ch := make(chan int3) // ch has type 'chan int',带缓存
  • channel使用—本质上是使用引用

和map类似,channel也对应一个make创建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。

  • channel 运算符
  1. ==
    两个相同类型的channel可以使用 == 运算符比较

  2. <-
    一个channel有发送和接受两个主要操作,都是通信行为。一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine。发送和接收两个操作都使用<-运算符。

ch <- x  // 发送语句
x = <-ch // 接受语句
<-ch     // 接受语句,也合法
  1. 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 执行发送操作而向队列插入元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值