[golang] channel通道

说明

channel是go当中的一个核心类型,可以看做是管道。并发核心单元可以通过channel进行数据的发送和接收,从而实现通信。

在go中,channel是一种数据类型,主要被用来解决协程的同步问题以及协程之间数据共享(数据传递)的问题。

go当中的goroutine运行在相同的地址空间,因此访问共享内存地址必须做好同步,goroutine奉行通过通信来共享内存,而不是共享内存来通信。

引用类型channel可用于多个goroutine通讯,在其内部实现了同步,确保并发安全。

定义channel变量

在go中,channel也一个对应make创建的底层数据结构的引用

当我们复制一个channel或者用于函数参数传递的时候,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象,和其他的引用类型一样,channel的零值也是nil。

定义一个channel的时候,也需要定义发送到channel的值的类型,channel可以使用内置的make函数来进行创建。

chan是创建channel使用的关键字,Type代表着channel收发数据的类型。

make(chan Type) // 等价于make(chan Type,0)
make(chan Type,capacity)

当 参数capacity= 0 时,channel 是无缓冲阻塞读写的;当capacity > 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入。

channel非常像生活中的管道,一边可以存放东西,另一边可以取出东西。channel通过操作符 <- 来接收和发送数据,发送和接收数据语法:

package main

import "fmt"

func main() {
    // 创建一个channel
    ch := make(chan int)
    // 创建一个子goroutine,在这个goroutine中进行数据的写入
    go worker(ch)
    // 读取channel通道中的数据
    num := <- ch
    fmt.Println("channel通道中读到的数据为:",num)
}

func worker(ch chan int) {
    // 向channel通道中写入数据
    ch <- 10
}

在上面的代码中,我们通过chan <- val 来进行channel值的写入,通过val := <- chan 来进行channel的值的读取。

我们首先创建了一个goroutine用来向channel管道中进行数据的写入,然后同时在main goroutine中进行数据的读取。

输出的结果为:

channel通道中读到的数据为: 10

Process finished with exit code 0

如果channel(无缓冲)的一头发生了读操作或者写操作,而在另外一头没有进行相对应的操作,那么就会发生阻塞,直到channel的另一头发生相应的操作。

默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得goroutine同步变的更加的简单,而不需要显式的lock。

package main

import (
    "fmt"
)

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

????go func() {
????????defer fmt.Println("子协程结束")

????????fmt.Println("子协程正在运行……")

????????c <- 666 //666发送到c
????}()

????num := <-c //从c中接收数据,并赋值给num

????fmt.Println("num = ", num)
????fmt.Println("main协程结束")
}

程序运行结果:

当进行读取数据的时候,可以采用下面的形式来判断数据是否传输完毕:

?x, ok := <-channel?

如果数据传输完毕,ok的值为false,否则为true。

package main

import "fmt"

func main() {
    // 创建一个channel
    ch := make(chan int)

    // 创建一个goroutine用来向channel中写入数据
    go func() {
        defer close(ch) // 通过close函数可以关闭channel通道
        for i:=0;i<5;i++ {
            ch <- i // 将i值写入channel
        }
    }()

    // 在主goroutine中读取channel通道传递的值
    for {
        val,ok := <-ch
        if !ok { // ok 为false时表示读取结束
            fmt.Println("读取结束,over")
            break
        }
        fmt.Println("读取到的数据为:",val)
    }
}

输出的结果为:

读取到的数据为: 0
读取到的数据为: 1
读取到的数据为: 2
读取到的数据为: 3
读取到的数据为: 4
读取结束,over

Debugger finished with exit code 0

定义无缓冲的channel

无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。

这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。

这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。

例如上面我们写的程序其实都是无缓冲的channel。

阻塞:由于某种原因数据没有到达,当前协程(线程)持续处于等待状态,直到条件满足,才接触阻塞。

同步:在两个或多个协程(线程)间,保持数据内容一致性的机制。

下图展示两个 goroutine 如何利用无缓冲的通道来共享一个值:

定义有缓冲的channel

有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个数据值的通道。

这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也不同。

只有通道中没有要接收的值时,接收动作才会阻塞。

只有通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。

这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。

示例图如下:

有缓冲的channel创建格式:

make(chan Type, capacity)

如果给定了一个缓冲区容量,通道就是异步的。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。

package main

import "fmt"

func main() {
    // 定义一个有缓冲的channel
    ch := make(chan int,2)

    // 创建一个goroutine,用来写入数据
    go func() {
        defer close(ch)
        for i:=0;i<5;i++ {
            ch <- i
            fmt.Println("写入的数据为:",i) // print操作属于io操作,可能导致cpu时间片切换
        }
    }()

    for {
        num,ok := <- ch
        if !ok {
            fmt.Println("读取结束,over")
            break
        }
        fmt.Println("读取到的数据为:",num)
    }
}

运行的结果为:

写入的数据为: 0
读取到的数据为: 0
读取到的数据为: 1
写入的数据为: 1
写入的数据为: 2
写入的数据为: 3
写入的数据为: 4
读取到的数据为: 2
读取到的数据为: 3
读取到的数据为: 4
读取结束,over

Process finished with exit code 0

关闭channel

如果发送者知道,没有更多的值需要发送到channel的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,因为接收者可以停止不必要的接收等待。这可以通过内置的close函数来关闭channel实现。

package main

import (
    "fmt"
)

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

????go func() {
????????for i := 0; i < 5; i++ {
????????????c <- i
????????}
????????//把 close(c) 注释掉,程序会一直阻塞在 if data, ok := <-c; ok 那一行
????????close(c)
????}()

????for {
????????//ok为true说明channel没有关闭,为false说明管道已经关闭
????????if data, ok := <-c; ok {
????????????fmt.Println(data)
????????} else {
????????????break
????????}
????}

????fmt.Println("Finished")
}

程序运行结果:

注意:

  • channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
  • 关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
  • 关闭channel后,可以继续从channel接收数据;
  • 对于nil channel,无论收发都会被阻塞。

可以使用 range 来迭代不断操作channel:

package main

import (
    "fmt"
)

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

????go func() {
????????for i := 0; i < 5; i++ {
????????????c <- i
????????}
????????//把 close(c) 注释掉,程序会一直阻塞在 for data := range c 那一行
????????close(c)
????}()

????for data := range c {
????????fmt.Println(data)
????}
????fmt.Println("Finished")
}

单向channel

默认情况下,通道channel是双向的,也就是,既可以往里面发送数据也可以同里面接收数据。

但是,我们经常见一个通道作为参数进行传递而值希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候我们可以指定通道的方向。

单向channel变量的声明非常简单,如下:

var ch1 chan int       // ch1是一个正常的channel,是双向的
var ch2 chan<- float64 // ch2是单向channel,只用于写float64数据
var ch3 <-chan int     // ch3是单向channel,只用于读int数据
//   chan<- //只写
func counter(out chan<- int) {
????defer close(out)
????for i := 0; i < 5; i++ {
????????out <- i //如果对方不读 会阻塞
????}
}

//   <-chan //只读
func printer(in <-chan int) {
????for num := range in {
????????fmt.Println(num)
????}
}

func main() {
????c := make(chan int) //   chan   //读写

????go counter(c) //生产者
????printer(c)    //消费者

????fmt.Println("done")
}

原文地址:https://www.cnblogs.com/liujunhang/p/12536331.html

时间: 2024-10-12 19:52:28

[golang] channel通道的相关文章

Go语言编程:使用条件变量Cond和channel通道实现多个生产者和消费者模型

如题,使用条件变量Cond和channel通道实现多个生产者和消费者模型.Go语言天生带有C语言的基因,很多东西和C与很像,但是用起来 绝对比C语言方便.今天用Go语言来实现下多消费者和生产者模型.如果对C语言的多生产者和消费者模型感兴趣的可以看Linux系统编程:使用mutex互斥锁和条件变量实现多个生成者和消费者模型 代码实现代码实现用了Cond条件变量和channel通道. package main import ( "fmt" "math/rand" &qu

golang 如何查看channel通道中未读数据的长度

可以通过内建函数len查看channel中元素的个数. 内建函数len的定义如下: func len(v Type) int The len built-in function returns the length of v, according to its type: Array: the number of elements in v.数组中元素的个数 Pointer to array: the number of elements in *v (even if v is nil).数组中

golang channel本质——共享内存

channel是golang中很重要的概念,配合goroutine是golang能够方便实现并发编程的关键.channel其实就是传统语言的阻塞消息队列,可以用来做不同goroutine之间的消息传递,由于goroutine是轻量级的线程能够在语言层面调度,所以channel在golang中也常被用来同步goroutine. 一般channel的声明形式为:var chanName chan ElementType ElementType指定这个channel所能传递的元素类型. 定义一个cha

java nio之 channel通道(二)

java nio 通道上一篇文章里就讲述过,channel总是写数据的时候,要先把数据写入到bytebuffer,读数据的时候总是要先从channel中读入到bytebuffer.如下图,这个图是好多知名博客常用的图,很好理解这个channel. channel分为一下几种: FileChannel SocketChannel ServerSocketChannel DatagramChannel FileChannel: 经常说的FileChannel都是拿下面的例子说事 代码如下: pack

Golang Channel用法简编

转自:http://tonybai.com/2014/09/29/a-channel-compendium-for-golang/ 在进入正式内容前,我这里先顺便转发一则消息,那就是Golang 1.3.2已经正式发布了.国内的golangtc已经镜像了golang.org的安装包下载页面,国内go程序员与爱好者们可以到"Golang中 国",即golangtc.com去下载go 1.3.2版本. Go这门语言也许你还不甚了解,甚至是完全不知道,这也有情可原,毕竟Go在TIOBE编程语

[Go] golang缓冲通道实现资源池

go的pool资源池:1.当有多个并发请求的时候,比如需要查询数据库2.先创建一个2个容量的数据库连接资源池3.当一个请求过来的时候,去资源池里请求连接资源,肯定是空的就创建一个连接,执行查询,结束后放入了资源池里4.当第二个请求过来的时候,也是去资源池请求连接资源,就直接在池中拿过来一个连接进行查询5.当并发大的时候,资源池里面没有足够连接资源,就会不停创建新资源,放入池里面的时候,也会放不进去,就主动关闭掉这个资源6.这里的资源池实质上是一个缓冲通道,里面放着连接资源 package mai

JAVA NIO(二)Channel通道

Channel概述 Channel是一个通道,可以通过它读取和写入数据,通道与流的不同之处在于通道是双向的,流是单向的:NIO中通过channel封装了对数据源的操作,通过channel 我们可以操作数据源,但又不必关心数据源的具体物理结构.在大多数应用中,channel与文件描述符或者socket是一一对应的.Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据.channel中所有数据都通过 Buffer 对象来处理.您永远不会将字节直接写入通道中

golang channel 用法

一.Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP,即Communicating Sequential Processes并发模型理论.但就像John Graham-Cumming所说的那样,多数Golang程序员或爱好者仅仅停留在“知道”这一层次,理解CSP理论的并不多,毕竟多数程序员是搞工程 的.不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本. 维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别: Actor模型广义

golang channel 使用总结

原文地址 不同于传统的多线程并发模型使用共享内存来实现线程间通信的方式,golang 的哲学是通过 channel 进行协程(goroutine)之间的通信来实现数据共享: Do not communicate by sharing memory; instead, share memory by communicating. 这种方式的优点是通过提供原子的通信原语,避免了竞态情形(race condition)下复杂的锁机制.channel 可以看成一个 FIFO 队列,对 FIFO 队列的读