关于golang:Channel

20次阅读

共计 2788 个字符,预计需要花费 7 分钟才能阅读完成。

为什么要用 channel?

单纯地将函数并发执行是没有意义的。函数与函数间须要替换数据能力体现并发执行函数的意义。

尽管能够应用共享内存进行数据交换,然而共享内存在不同的 goroutine 中容易产生竞态问题。为了保障数据交换的正确性,必须应用互斥量对内存进行加锁,这种做法势必造成性能问题。

Go 语言的并发模型是 CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。

如果说 goroutine 是 Go 程序并发的执行体,channel 就是它们之间的连贯。channel 是能够让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。

Go 语言中的通道(channel)是一种非凡的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规定,保障收发数据的程序。每一个通道都是一个具体类型的导管,也就是申明 channel 的时候须要为其指定元素类型。

创立 channel

通道是援用类型,通道类型的空值是 nil。

var ch chan int
fmt.Println(ch) // <nil>

申明的通道后须要应用 make 函数初始化之后能力应用。

创立 channel 的格局如下:

    make(chan 元素类型, [ 缓冲大小])

channel 的缓冲大小是可选的。

举几个例子:

ch4 := make(chan int)
ch5 := make(chan bool)
ch6 := make(chan []int)

channel 操作

通道有发送(send)、接管 (receive)和敞开(close)三种操作。

发送和接管都应用 <- 符号。

当初咱们先应用以下语句定义一个通道:

ch := make(chan int)

发送

将一个值发送到通道中。

ch <- 10 // 把 10 发送到 ch 中 

接管

从一个通道中接管值。

x := <- ch // 从 ch 中接管值并赋值给变量 x
<-ch       // 从 ch 中接管值,疏忽后果 

敞开

咱们通过调用内置的 close 函数来敞开通道。

    close(ch)

对于敞开通道须要留神的事件是,只有在告诉接管方 goroutine 所有的数据都发送结束的时候才须要敞开通道。通道是能够被垃圾回收机制回收的,它和敞开文件是不一样的,在完结操作之后敞开文件是必须要做的,但敞开通道不是必须的。

敞开后的通道有以下特点:

    1. 对一个敞开的通道再发送值就会导致 panic。2. 对一个敞开的通道进行接管会始终获取值直到通道为空。3. 对一个敞开的并且没有值的通道执行接管操作会失去对应类型的零值。4. 敞开一个曾经敞开的通道会导致 panic。

无缓冲的通道

无缓冲的通道又称为阻塞的通道,咱们来看一下上面的代码:

func main() {ch := make(chan int)
    ch <- 10
    fmt.Println("发送胜利")
}

下面这段代码可能通过编译,然而执行的时候会呈现以下谬误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        .../go/src/test/main.go:8 +0x54
exit status 2

为什么会呈现 deadlock 谬误呢?

因为咱们应用 ch := make(chan int) 创立的是无缓冲的通道,无缓冲的通道只有在有人接管值的时候能力发送值。

解决上述问题能够启用一个 goroutine 去接管值,例如:

func recv(c chan int) {
    ret := <-c
    fmt.Println("接管胜利", ret)
}
func main() {ch := make(chan int)
    go recv(ch) // 启用 goroutine 从通道接管值
    ch <- 10
    fmt.Println("发送胜利")
}

无缓冲通道上的发送操作会阻塞,直到另一个 goroutine 在该通道上执行接管操作,这时值能力发送胜利,两个 goroutine 将继续执行。相同,如果接管操作先执行,接管方的 goroutine 将阻塞,直到另一个 goroutine 在该通道上发送一个值。

应用无缓冲通道进行通信将导致发送和接管的 goroutine 同步化。因而,无缓冲通道也被称为同步通道。

有缓冲的通道

咱们能够在应用 make 函数初始化通道的时候为其指定通道的容量,例如:

func main() {ch := make(chan int, 1) // 创立一个容量为 1 的有缓冲区通道
    ch <- 10
    fmt.Println("发送胜利")
}

只有通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量示意通道中能寄存元素的数量。

如何判断通道敞开

ok 法

package main

import ("fmt")

func main() {ch := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {ch <- i}
        close(ch)
    }()

    for {
        if data, ok := <-ch; ok {fmt.Println(data)
        }else {break}
    }
    fmt.Println("main 函数执行结束")
}

for-range 法

package main

import ("fmt")

func main() {ch1 := make(chan int)
    ch2 := make(chan int)

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

    go func() {
        for {
            i, ok := <-ch1
            if !ok{break}
            ch2 <- i * i
        }
        close(ch2)
    }()

    for i := range ch2{fmt.Println(i)
    }
}

下面两种判断通道是否敞开的办法第二种比拟优雅,个别罕用第二种办法。

单向通道

有的时候咱们会将通道作为参数在多个工作函数间传递,很多时候咱们在不同的工作函数中应用通道都会对其进行限度,比方限度通道在函数中只能发送或只能接管。

Go 语言中提供了单向通道来解决这种状况。例如,咱们把下面的例子革新如下:

package main

import ("fmt")

func counter(out chan<- int){
    for i := 0;i < 10; i++{out <- i}
    close(out)
}

func squarer(out chan<- int, in <-chan int){
    for i := range in{out <- i * i}
    close(out)
}

func printer(in <-chan int){
    for i := range in{fmt.Println(i)
    }
}

func main() {ch1 := make(chan int)
    ch2 := make(chan int)

    go counter(ch1)
    go squarer(ch2, ch1)

    printer(ch2)
}

其中,

    1.<-chan int 是一个只能发送的通道,能够发送然而不能接管;2.chan<- int 是一个只能接管的通道,能够接管然而不能发送。

正文完
 0