Go语言的并发(多线程协程)通道(信道)缓冲信道(Buffer Channels)长度和容量

Posted 之墨_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言的并发(多线程协程)通道(信道)缓冲信道(Buffer Channels)长度和容量相关的知识,希望对你有一定的参考价值。

Go的并发

有的地方称为协程

Go 语言可以通过 go 关键字来开启 goroutine 即可实现多线程的并发任务处理。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

  • 我们通过这一个实例认识一下:
package main

import (
        "fmt"
        "time"
)

func say(s string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }
}

func main() {
        go say("world")
        say("hello")
}

在运行结果中,hello和word是随机出现的,说明并发模式下执行的语句没有先后顺序,并且每次运行的结果都是不同的

通道(channel)

通道(channel)是用来传递数据的一个数据结构。

  • 通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
  • 操作符 <- 用于指定通道的方向,发送或接收。
  • 如果未指定方向,则为双向通道。
    ch <- v // 把 v 发送到通道 ch
    v := <-ch // 从 ch 接收数据 、并把值赋给 v

创建一个’通道’可以使用关键字chan,并且在使用前必须创建

一个实例:

package main

import "fmt"

func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
                sum += v
        }
        c <- sum // 把 sum 发送到通道 c
}

func main() {
        s := []int{7, 2, 8, -9, 4, 0}

        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c // 从通道 c 中接收

        fmt.Println(x, y, x+y)
}

这段代码的效果是通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和

缓冲信道(Buffer Channels)

我们可以创建一个有缓冲(Buffer)的信道。只在缓冲已满的情况,才会阻塞向缓冲信道(Buffered Channel)发送数据。同样,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据。

  • 通过向 make 函数再传递一个表示容量的参数(指定缓冲的大小),可以创建缓冲信道。
ch := make(chan type, capacity)
  • 要让一个信道有缓冲, capacity(容量) 应该大于 0。无缓冲信道的容量默认为 0。
    这个例子创建了一个缓冲信道。
package main

import (  
    "fmt"
)


func main() {  
    ch := make(chan string, 2)
    ch <- "naveen"
    ch <- "paul"
    fmt.Println(<- ch)
    fmt.Println(<- ch)
}

在上面程序里的第 9 行,我们创建了一个缓冲信道,其容量为 2。由于该信道的容量为 2,因此可向它写入两个字符串,而且不会发生阻塞。在第 10 行和第 11 行,我们向信道写入两个字符串,该信道并没有发生阻塞。我们又在第 12 行和第 13 行分别读取了这两个字符串

该程序输出结果为

naveen
paul

下面缓冲信道的实例中有一个并发的 Go 协程来向信道写入数据,而 Go 主协程负责读取数据。这个例子解释了向缓冲信道写入数据时,什么时候会发生阻塞。

package main

import (  
    "fmt"
    "time"
)

func write(ch chan int) {  
    for i := 0; i < 5; i++ {
        ch <- i
        fmt.Println("successfully wrote", i, "to ch")
    }
    close(ch)
}
func main() {  
    ch := make(chan int, 2)
    go write(ch)
    time.Sleep(2 * time.Second)
    for v := range ch {
        fmt.Println("read value", v,"from ch")
        time.Sleep(2 * time.Second)

    }
}

在上面的程序中,在Go 主协程中创建了容量为 2 的缓冲信道 ch,而第 17 行把 ch 传递给了 write 协程接下来 Go 主协程休眠了两秒。在这期间,write 协程在并发地运行。write 协程有一个 for 循环,依次向信道 ch 写入 0~4。而缓冲信道的容量为 2,因此 write 协程里立即会向 ch 写入 0 和 1接下来发生阻塞直到 ch 内的值被读取。因此,该程序立即打印出下面两行:
successfully wrote 0 to ch
successfully wrote 1 to ch
打印上面两行之后,write 协程中向 ch 的写入发生了阻塞,直到 ch 有值被读取到。而 Go 主协程休眠了两秒后,才开始读取该信道,因此在休眠期间程序不会打印任何结果。主协程结束休眠后,在第 19 行使用 for range 循环,开始读取信道 ch,打印出了读取到的值后又休眠两秒,这个循环一直到 ch 关闭才结束。所以该程序在两秒后会打印下面两行:

successfully wrote 0 to ch
successfully wrote 1 to ch

  • 该过程会一直进行,直到信道读取完所有的值,并在 write 协程中关闭信道。最终输出如下:

successfully wrote 0 to ch
successfully wrote 1 to ch
read value 0 from ch
successfully wrote 2 to ch
read value 1 from ch
successfully wrote 3 to ch
read value 2 from ch
successfully wrote 4 to ch
read value 3 from ch
read value 4 from ch

长度与容量

  • 缓冲信道的容量是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小。

  • 缓冲信道的长度是指信道中当前排队的元素个数。

package main

import (  
    "fmt"
)

func main() {  
    ch := make(chan string, 3)
    ch <- "naveen"
    ch <- "paul"
    fmt.Println("capacity is", cap(ch))
    fmt.Println("length is", len(ch))
    fmt.Println("read value", <-ch)
    fmt.Println("new length is", len(ch))
}

在这个程序中,我们创建了一个容量为 3 的信道,于是它可以保存 3 个字符串。
接下来分别在第 9 行和第 10行向信道写入了两个字符串。于是信道有两个字符串排队,因此其长度为 2
在第13行,又从信道读取了一个字符串。现在该信道内只有一个字符串,因此其长度变为 1。

该程序会输出:

capacity is 3
length is 2
read value naveen
new length is 1

以上是关于Go语言的并发(多线程协程)通道(信道)缓冲信道(Buffer Channels)长度和容量的主要内容,如果未能解决你的问题,请参考以下文章

go 语言 并发,并行,信道

GoLang协程与通道---上

Golang✔️走进 Go 语言✔️ 第十六课 协程 & 通道

Golang✔️走进 Go 语言✔️ 第十六课 协程 & 通道

Go 缓冲信道

云原生训练营模块二 Go语言进阶