【Go语言基础语法】Channel

Channel

  Go 语言中的 Channel 是一种通信机制,用于在多个 Goroutine 之间共享数据。它是 Go 语言中的一等公民,可以像其他数据类型一样被定义和使用。

  Channel 是一个先进先出(FIFO)的队列,可以将它理解为一个管道,数据可以通过这个管道在 Goroutine 之间进行传递。当一个 Goroutine 向 Channel 发送数据时,另一个 Goroutine 可以从 Channel 中接收这个数据。

  Channel 的类型可以是无缓冲的(unbuffered)或有缓冲的(buffered)。对于无缓冲的 Channel,发送数据的 Goroutine 必须等到接收数据的 Goroutine 从 Channel 中取出数据后才能继续执行;对于有缓冲的 Channel,发送数据的 Goroutine 不必等待,可以继续执行,直到缓冲区满了为止。

  使用 Channel 可以实现生产者-消费者模式,其中生产者不断向 Channel 发送数据,而消费者不断从 Channel 中接收数据并进行处理。通过使用 Channel,可以确保数据在多个 Goroutine 之间安全地共享和传递。

  接下来,我们通过几段代码来进一步分析 Channel 机制。

E.g 1

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            c <- i
        }
    }()

    go func() {
        for {
            fmt.Println(<-c)
        }
    }()

    time.Sleep(time.Second)
}

  这段代码定义了一个整数类型的无缓冲 Channel c,然后启动了两个 Goroutine。第一个 Goroutine 循环向 c 发送整数 0 到 9,第二个 Goroutine 循环从 c 接收并打印接收到的值。

  在发送完 10 个整数后,第一个 Goroutine 退出,此时第二个 Goroutine 开始从 c 中读取值。由于 c 是无缓冲的,因此第二个 Goroutine 必须等待,直到第一个 Goroutine 发送数据。读取数据后,第二个 Goroutine 继续循环读取下一个值,直到 c 中没有更多数据为止。

  最后,程序会等待一秒钟,以便两个 Goroutine 有足够的时间完成它们的任务,然后退出。

E.g 2

package main

import (
    "fmt"
)

func main() {
    c := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            c <- i
        }
        close(c)
    }()

    for n := range c {
        fmt.Println(n)
    }
}

  这段代码与之前的代码类似,只是使用了有缓冲的 Channel。与之前的代码不同的是,在第一个 Goroutine 发送完 10 个整数后,它会调用 close(c) 函数来关闭 c 通道。这意味着发送数据的 Goroutine 不再向 c 发送数据,同时接收数据的 Goroutine 可以知道 c 中已经没有更多的数据了。

  由于 c 是有缓冲的,因此在 close(c) 之前,接收数据的 Goroutine 仍然可以从 c 中读取数据。只有当 c 中的缓冲区都被读取完后,接收数据的 Goroutine 才会因为 c 被关闭而退出循环。

  通过使用 close(c) 函数,可以确保在发送数据的 Goroutine 完成后,接收数据的 Goroutine 能够正确地退出。

E.g 3

package main

import "fmt"

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    for _ = range values {
        <-done
    }
}

  这段代码定义了一个布尔类型的有缓冲 Channel done,用于通知所有的 Goroutine 已经完成。然后,它启动了三个 Goroutine,每个 Goroutine 都会打印一个字符串并向 done 发送一个 true 值。

  在循环遍历完所有的字符串后,主 Goroutine 会从 done 中读取值,直到接收到所有 Goroutine 发送的 true 值。这意味着主 Goroutine 必须等待所有的 Goroutine 完成后才能继续执行。在这儿我们通过使用 done 通道,可以确保在所有的 Goroutine 完成后,主 Goroutine 能够正确地退出。

  现在,让我们猜猜这段代码的输出是什么?你可能会以为是 a b c ,然而实际上它的输出是 c c c !这是为什么呢?

  问题出在闭包上。由于匿名函数捕获了外部循环中的变量 v,而 Goroutine 执行的时间可能是在循环迭代之后。因此,所有的 Goroutine 最终都读取了v的最终值,即最后一个字符串 “c”。

  当你运行代码时,Goroutines 在后台执行,但在它们开始执行之前,主循环可能已经完成迭代,变量v的值变成了最后一个字符串 “c”。这导致所有的 Goroutine 都会打印出最后一个字符串。

  想要解决这个问题,我们可以在启动每个goroutine时,将v的值作为参数传递给匿名函数,以确保每个goroutine捕获的是不同的字符串值。

for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    }(v)
}

  像这样修改代码,那么每个 Goroutine 都会捕获当前循环迭代的字符串值,而不会受到后续迭代的影响。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值