Go中的Channel


2022年1月17日, Learn eTutorial
2128

在本教程中,您将学习Go语言中的Channel概念。Go语言中的Channel与我们之前教程中讨论的Go语言中的Goroutine相关。您可以先参考Go routine,然后才能理解本教程将要讨论的Channel。

Go中的Channel是什么?

在Go语言中,Channel是一种允许Goroutine之间共享数据的机制。Goroutine允许Go程序中的活动并发执行,这些活动在Goroutine之间共享数据或资源。Go语言支持成千上万的Goroutine在执行过程中共享数据或资源以实现并发。(我们已经在Goroutine教程中讨论过这一点)

  •    Go语言中的Channel是Goroutine之间进行通信的媒介。
  •  换句话说,Channel是一个管道,允许Goroutine执行诸如读/写等特定操作。
  •  通过Channel,Goroutine传输它们的数据或资源,一个Goroutine向Channel写入数据,另一个Goroutine从同一个Channel读取数据。 
GO : Channels

Goroutine A将一些数据写入Channel,Goroutine B从同一个Channel读取数据。因此,Channel(路径)有助于Goroutines之间的数据传输或通信。

Go中Channel的必要性是什么?

为了更好地理解Channel的概念,我们将简要回顾一下Goroutine中发生的事情。
让我们用我们在Goroutine教程中讨论的同一个例子来理解。


package main
import (
  "fmt"
  "time"
)

func main() {
    
    fmt.Println("Go program starts")
    msg :="learn eTutorial"
    go func(){  //anonymous function
    fmt.Println(msg)
    }()
    msg = "hello users"
    fmt.Println("Go program ends")
    time.Sleep(100 * time.Millisecond)
    }


上面给出的程序包含两个代码块。

  1.     main() 函数
  2.     hello()
GO : Channels

这两个代码段独立执行。由于main()和hello()函数Goroutine的独立执行,出现了以下问题:

  1.  有时程序会在未执行hello() Goroutine的情况下结束/终止,因此我们使用sleep()语句来解决这个问题。
  2.  另一个问题是您无法在main()函数中访问hello()函数的结果。

如何在Go中声明Channel?

使用新的keyword chan在Go中声明Channel。
声明语法 


var <variable name> chan <data type> 

GO : Channels

这里c是一个通道类型的变量,用chan关键字声明,数据类型为整数(int)。

注意: 声明时Channel的默认值为nil,与我们在Goroutine中讨论的一样。

如何在Goroutines之间添加Channel?

在Go语言中,Channel是允许我们Go程序中特定Goroutines之间有效通信的结构。Go Channel的概念自1970年代中期就存在了。
让我们看看如何实例化一个Channel并将其用于不同Goroutines之间的通信。
我们将逐步理解。

  1.  第一步是打开main()函数并实例化一个名为msgs的新Channel。
  2.  为了实例化或创建Channel,我们使用“make”关键字,后跟chan关键字及其声明的type。在给定的程序中,数据类型是整数。
  3.  接下来,在程序中调用defer关键字来关闭Channel。
  4.  关闭操作会阻止Goroutines向该Channel发送或接收。
  5.  为了在Go中与Channel进行交互,我们使用发送或接收运算符。
  6.  创建了一个SendMsg Goroutine函数,它接收一个Channel,并定义了发送运算符(<- ),用于向此Channel发送值。
  7. 这是通过在main函数中调用 go SendMsg(msgs)发送到Channel的。
  8.  然后我们将从SendMsg接收值,为了接收,使用接收运算符,它会阻塞直到值被发送到该Channel。
  9.  当我们运行代码时,可以看到值已成功从Goroutine函数发送到变量msg。这会在输出中显示。这是一个简单的整数Channel示例。

注意: Channel使用make函数创建,该函数指定chan关键字和Channel的元素类型。

当我们创建一个未指定Channel大小的Channel时,它被称为未缓冲Channel。您将在下一节中学习。


package main

import (
    "fmt"
)

func SendMsg(c chan int){
 c <-0
}
func main() {
    fmt.Println("Learn eTutorial")

   msgs := make(chan int) // instantiating a channel
   defer close(msgs)
    go SendMsg(msgs)

    msg := <-msgs
    fmt.Println(msg)
}

输出


Learn eTutorial
0

Go中有哪些类型的Channel?

根据数据在Goroutines之间的传输方式,Channel有两种类型。它们是:

  1.  未缓冲Channel
  2.  缓冲Channel
     
GO : Channels

Go中的未缓冲Channel是什么?

让我们考虑上面描述的相同程序,但唯一的区别是我们创建了一个字符串数据类型的Channel。

  •  当创建一个未指定Channel大小时,技术上称为未缓冲Channel。
  •  在未缓冲Channel中,只有当发送方和接收方都准备好时,Goroutines之间的通信才会成功。
  •  在任何Go程序中的传统Channel中,可能会有一些意外行为。
  •  在未缓冲Channel中,每当Goroutine A向Channel发送一个值时,Goroutine A将被阻塞,直到该值从该Channel接收。

为了更好地理解相同的概念,对上面给出的代码进行了一些修改:


package main

import (
    "fmt"
    "time"
)

func SendMsg(c chan string){   
fmt.Println("start Goroutine execution")
time.Sleep(1 * time.Second)
 c <-"hello world"
 fmt.Println("finished Goroutine execution")
}

func main() {
    fmt.Println("Learn eTutorial")

   msgs := make(chan string)    //created channel of string type.
   defer close(msgs)

    go SendMsg(msgs)
    go SendMsg(msgs)

    msg := <-msgs
    fmt.Println(msg)
}

输出


Learn eTutorial
start Goroutine execution
start Goroutine execution
finished Goroutine execution
hello world

func SendMsg(c chan string){   
fmt.Println("start Goroutine execution")
time.Sleep(1 * time.Second)
 c <-"hello world"
 fmt.Println("finished Goroutine execution")
}

在这部分代码中,定义了两个打印语句(“开始Goroutine执行”)和(“完成Goroutine执行”)。Sleep语句允许Goroutine睡眠几秒钟。在向Channel发送操作,即c <- "hello world"之后,打印出Goroutine执行已完成。
还重复了go SendMsg(msgs)语句。有两个Goroutines正在尝试发送到同一个Channel。
在上述程序中,只有一个接收语句   msg := <-msgs  到Channel,因此只有一个Goroutine预计会完成其执行。只有一个Goroutine会执行并打印最后的打印语句。

第二个Goroutine不会完成其执行,被阻塞在main函数中。程序将在第二个Goroutine完成执行之前终止。

Go中的缓冲Channel是什么?

为了避免阻塞行为,我们指定了Channel的大小。当一个Channel在创建时指定了其大小时,它被称为缓冲Channel。
语法 


Buffered channel: = make (chan datatype, size)

示例


msgs: = make (chan string, 2)    

通过将其更改为缓冲Channel,发送操作,c <-"hello world" 只会在Goroutines中阻塞。


import (
    "fmt"
    "time"
)

func SendMsg(c chan string){   
fmt.Println("start Goroutine execution")
time.Sleep(1 * time.Second)
 c <-"hello world"
 fmt.Println("finished Goroutine execution")
}

func main() {
    fmt.Println("Learn eTutorial")

 msgs := make(chan string,2)    //created channel of string type.
  defer close(msgs)

    go SendMsg(msgs)
    go SendMsg(msgs)

    msg := <-msgs
    fmt.Println(msg)
    time.Sleep(1 * time.Second)
}

输出


Learn eTutorial
start Goroutine execution
start Goroutine execution
finished Goroutine execution
hello world
finished Goroutine execution

在最后提供了一个sleep()语句,因为不能保证第二个Goroutine在第一个Goroutine退出之前完成。

从输出中可以清楚地看到,第一个和第二个Goroutine开始执行,同样,两者都完成。
当我们在没有可用接收者时不想让Channel语句阻塞时,可以使用缓冲Channel。添加缓冲区允许我们等待一些接收者被释放,而不会阻塞发送代码。

Go中Channel的属性是什么?

  •   Channel提供阻塞和解除阻塞Goroutines的机制。
  •   Channel提供FIFO语义
  •   Goroutines之间安全可靠的数据传输。
  •  易于创建和使用

Go中的Channel操作是什么?

Channel提供的基本操作是读和写。读写Channel用左箭头<-表示。

GO : Channels

注意: 相同的运算符也表示Channel中的发送/接收操作。

  •  Goroutines之间的通信基于这两个操作,即发送和接收。
  •  <- Channel运算符指示数据是由任何Goroutine接收还是发送。
  •  默认情况下,发送和接收操作处于阻塞状态,直到另一方准备好运行。
  • 这种具有阻塞状态的操作允许实现Goroutines之间的同步。
  • 没有使用条件变量或锁来进行同步,只有这两个操作成功地完成了同步。
GO : Channels

这里Channel发送和接收数据。让蓝色的圆圈代表一个字符串“hello world”消息。

发送操作:这表示消息“hello world”(数据)被发送到创建为c的Channel。(上面各节已讨论过Channel的创建)。
                                                 c <-"hello world" 

接收操作:在此操作中,Goroutine A发送到Channel的消息由另一个Goroutine B接收,通过创建一个名为msg的新变量。
                                                msg  := <-c
 

// Go程序说明发送和接收操作


package main
  
import (
"fmt"
"time"
)  
func myfunc(c chan string) {
  
    fmt.Println("start Goroutine execution")
   time.Sleep(1 * time.Second)
   c <-"hello world"                             //send msg hello world to channel c
   fmt.Println("finished Goroutine execution")

}
func main() {
    fmt.Println("start Main method")
                                                               // Creating a channel
    c := make(chan string)
  
    go myfunc(c)
    msg := <-c                                        //receive msg helloworld from channel c
    fmt.Println(msg)
    fmt.Println("End Main method")
}

输出


start Main method
start Goroutine execution
finished Goroutine execution
hello world
End Main method