Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Channel 基础概念和用法 #54

Open
kevinyan815 opened this issue Feb 7, 2021 · 0 comments
Open

Channel 基础概念和用法 #54

kevinyan815 opened this issue Feb 7, 2021 · 0 comments

Comments

@kevinyan815
Copy link
Owner

kevinyan815 commented Feb 7, 2021

Go 语言中最常见的、也是经常被人提及的设计模式就是:不要通过共享内存的方式进行通信,应该通过通信的方式共享内存。在很多主流的编程语言中,多个线程传递数据的方式一般都是共享内存,为了解决线程竞争,我们需要通过加互斥锁的方式限制同一时间能够读写这些变量的线程数量。

Thread1 ====> Memory ====> Thread2

然而这与 Go 语言鼓励的设计并不相同。

虽然我们在 Go 语言中也能使用共享内存加互斥锁进行通信,但是 Go 语言提供了一种不同的并发模型,即通信顺序进程(Communicating sequential processes,CSP)。goroutine 和 Channel 分别对应 CSP 中的实体和传递信息的媒介,goroutine 之间会通过 Channel 传递数据。

goroutine1 ====> Channel ====> goroutine2

上面两个 goroutine,一个会向 Channel 中发送数据,另一个会从 Channel 中接收数据,它们两者能够独立运行并不存在直接关联,但是能通过 Channel 间接完成通信。

目前的 Channel 收发操作均遵循了先进先出的设计,具体规则如下:

  • 先从 Channel 读取数据的 goroutine 会先接收到数据;
  • 先向 Channel 发送数据的 goroutine 会得到先发送数据的权利;

基本用法

可以往 Channel 中发送数据,也可以从 Channel 中接收数据,所以,Channel 类型分为:只接收、只发送、双向收发 三种类型。

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

相应的语法定义如下:

类型声明

var chan1 chan int // 可以双向收发int数据的 双向chan

var chan2 <-chan int // 只能从通道接收到int数据的 单向chan

var chan3 chan <- int //只能向 chan 发送int 数据的 单向chan

初始化

未初始化的 chan 的零值是 nil,不能直接使用。通过内置的 make 函数,我们可以初始化一个 chan

make(chan int, 100)

上面我们初始化了一个容量为100的 chan ,我们把这样的 chan 叫做 buffered chan。如果没有设置容量,那么容量默认是 0,我们把这样的 chan 叫做 unbuffered chan。

make(chan int)

对于 buffered chan,如果 chan 的缓存循环列表中还有数据,那么goroutine 从 chan 接收数据的时候就不会阻塞,如果 chan 的容量还未填满,goroutine 给它发送数据时也不会阻塞,否则就会发送阻塞。unbuffered chan 只有读写都准备好之后才不会阻塞

发送数据

往通道中发送一个数据使用 ch<-,发送数据是一条语句:

ch <- 2000

这里的 ch 是 chan int 类型或者是 chan <-int 类型。

接收数据

从通道中接收一条数据使用 <-ch,接收数据也是一条语句:

x := <-ch // 把接收的一条数据赋值给变量x
foo(<-ch) // 把接收的一个的数据作为参数传给函数
<-ch // 丢弃接收的一条数据

这里的 ch 类型是 chan T 或者 <-chan T。

从通道中接收数据时,还可以返回两个值。

v, ok := <-ch

第一个值 v 存储从 chan 中读取到的元素,第二个值是 bool 类型,代表是否成功地从 chan 中读取到一个值,如果第二个参数是 false,表明 chan 已经被 close 而且 chan 中没有缓存的数据,这个时候,第一个值是零值。所以,如果从 chan 读取到一个零值,可能是 sender 真正发送的零值,也可能是 chan 已经被 close 并且已经没有缓存元素而产生的零值。

其他操作

Go 内建的函数 close、cap、len 都可以操作 chan 类型:close 会把 chan 关闭掉,cap 返回 chan 的容量,len 返回 chan 中缓存的还未被取走的元素数量。

send 和 recv 都可以作为 select 语句的 case clause,如下面的例子:

func main() {
    var ch = make(chan int, 10)
    for i := 0; i < 10; i++ {
        select {
        case ch <- i:
        case v := <-ch:
            fmt.Println(v)
        }
    }
}

chan 还可以应用于 for-range 语句中,比如:

    for v := range ch {
        fmt.Println(v)
    }

在通道被关闭后,会自动退出for range 循环的执行。

下面的可以用于抽干Channel内缓存的元素

for range ch {
}

// 或者 
for _ := range ch {

}

Channel和并发同步原语怎么选择

Channel 并不能用于解决所有并发编程中的问题,有些场景适合使用Channel,而有的场景使用并发同步原语更加简单。 具体什么时候用Channel 什么时候用 sync 原语,可以用一下标准衡量:

  • 共享资源的并发访问使用 sync 原语;
  • 复杂的任务编排和数据传递使用 Channel;
  • 消息通知机制使用 Channel;
  • 单次广播用Channel,多次广播用sync.Cond 的 Broadcast (通道不支持关闭多次无法实现多次广播)。
  • 等待所有任务的完成用 WaitGroup,也有 Channel 的推崇者用 Channel,都可以;
  • 保护资源,限制访问资源的最大线程数用信号量 Seamphore
  • 需要和 Select 语句结合,使用 Channel;
  • 需要和超时配合时,使用 Channel 和 Context。
@kevinyan815 kevinyan815 changed the title Channel Channel 基础概念和用法 Feb 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant