Golang Channel通道使用简介

Golang Channel通道使用简介

一、channel简介

chan 是 Golang 中内置的数据类型,不像 Mutex 等需要引入,它是 first-class 类型,在 Go 的并发控制中起着相当重要的作用。chan 的思想来自 Tony Hoare 在 1978 年发表的论文 Communicating Sequential Processes, 它提出了一种并发的编程语言,用来描述并发系统中的互动模式,后来逐渐形成了 CSP 并发模式。CSP 模式中存在 Process/Channel 每个 Process 独立运行,多个 Process 之间通过 Channel 通信。

Go 并发控制的核心思想:

Don’t communicate by sharing memory, share memory by communicating. 
即不要通过共享内存来通信,而是要通过通信来共享内存,

前一句话对应传统利用 锁 等方式控制并发,后者就对应 CSP方式,go 中的 goroutine/chan 分别与 CSP 中的 Process/Channel 对应。

二、channel基础知识

基本分类

chan    // 既可以发送数据,又可以接收数据的 channel
chan<-  // 只能接收数据的 channel
<-chan  // 只能发送数据的 channel

channel 是管道,因此一个传输具体类型数据的 channel 声明方式如下:

var ch chan string      // 双向通道, 既可以往 ch 中写,也可以从 ch 中读 string 类型数据
var ch chan<- interface{} 或者 var ch = make(chan<- interface{}) // 只能往 ch 中发送 struct{} 类型数据
var ch <-chan int      或者 var ch make(<-chan int)        // 只能从 ch 中读取 int 数据

// 只读和只写一般用于数据通道,单独使用没有意义,比如:
func main() {
    c := make(chan int)
    go send(c)
    go recv(c)
    time.Sleep( * time.Second)
}
//只能向chan里写数据
func send(c chan<- int) {
    for i := ; i < ; i++ {
        c <- i
    }
}
//只能取channel中的数据
func recv(c <-chan int) {
    for i := range c {
        fmt.Println(i)
    }
}

channel 使用必须初始化,初始化是否指定长度可以分为

ch := make(chan string) // 无缓冲通道
ch := make(chan string, 2)// 有缓冲通道
三、基本使用
1) 发送数据

往 channel 中发送数据使用 “ch<-”,示例如下:

ch <- 10

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

2) 接收数据

从 channel 中接收数据使用 “<-ch”,示例如下:

x := <-ch  // 把 ch 中接收到的一条数据赋值给 x
foo(<-ch) // 把 ch 中接收到的一条数据作为函数 foo 的参数
<-ch      // 丢弃一条从 ch 中接收到的数据

从 channel 中接收数据的时候,还可以接收两个值:

x, ok := <-ch

ok 是一个 bool 值,表示是否成功的从 channel 中接收到了数据。如果 ok 是 false,ch 已经被 close,且 ch 中没有缓存数据,那么 x 就是零值。所以,如果 x 是零值,有可能是接收到了零值,也有可能是空的且被 close 的 channel 产生的零值。

3) 其它操作

Go 的内建函数 close、cap、len 都可以操作 channel。close 可以关闭 channel,关闭之后的 channel 无法接收数据;cap 返回 channel 的容量;len 返回 channel 的长度。

发送和接收数据还可以作为 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)
        }
    }
}

channel 还可以用于 for-range 语句中:

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

或者是忽略读取的值,只是清空 channel:

for range ch {
}
四、踩坑注意
1) panic 错误

常见的会导致 panic 的三种场景如下:

  • close 为 nil 的 channel
  • close 已经 closed 的 channel
  • send 已经 close 的 channel
2) goroutine 泄露

无缓冲chan的使用,必须要求接收方和发送方都准备好,如果仅仅发送方准备好就发送信息,会导致chan阻塞,例如:

var ch = make(chan int)
ch <- -1
for {
	select {
        case res := <-ch:
		fmt.Println("收到信息",res)
	}
}

该示例中,对于无缓冲信道,在接收者未准备好之前,发送操作是阻塞的.,goroutine 无法退出,进而造成泄露。

解决方案1:让接收者提前做好准备,随时准备接受信息,推荐使用。

var ch = make(chan int)
go func() {
	for {
		select {
		case res := <-ch:
			fmt.Println("收到信息",res)
		}
	}
}()
ch <- -1 

解决方案2:使用有缓冲通道

var ch = make(chan int,1)
ch <- -1
fmt.Println("收到信息", <-ch)
3) 容量导致阻塞

每个缓冲信道,都有容量,当信道里的数据量等于信道的容量后,此时再往信道里发送数据,就失造成阻塞,必须等到有人从信道中消费数据后,程序才会往下进行。

func main() {
	ch1 := make(chan string, 1)

	ch1 <- "hello world"
	ch1 <- "hello China"// 超过容量,排队阻塞

	fmt.Println(<-ch1)
}
4) 持续读无写导致死锁

当程序一直在等待从信道里读取数据,而此时并没有人会往信道中写入数据。此时程序就会陷入死循环,造成死锁。

func main() {
	pipline := make(chan string)
	go func() {
		pipline <- "hello world"
		pipline <- "hello China"
	}()
	for data := range pipline{
		fmt.Println(data)
	}
}

解决:只要在发送完数据后,手动关闭信道,告诉 range 信道已经关闭,无需等待就行。

func main() {
	pipline := make(chan string)
	go func() {
		pipline <- "hello world"
		pipline <- "hello China"
		close(pipline)
	}()
	for data := range pipline{
		fmt.Println(data)
	}
}
### GolangChannel 的用法与示例 ChannelGo 语言中的一个重要特性,用于在 Goroutines 之间安全地传递数据。它提供了一种简洁的方式来实现并发控制和通信。 #### 创建和初始化 Channel 可以使用 `make` 函数创建一个新的 channel。下面是一个简单的例子: ```go ch := make(chan int) ``` 此代码片段定义了一个整数类型的 channel[^1]。 #### 发送和接收数据 通过 `<-` 运算符可以在 channel 上发送或接收数据。以下是基本操作的例子: ```go // 向 channel 发送数据 ch <- value // 从 channel 接收数据 value := <-ch ``` 这些语句分别表示向 channel 发送数据和从 channel 获取数据的操作[^2]。 #### 使用带缓冲区的 Channel 无缓冲的 channel 只有当接收方准备好时才会继续执行;而带缓冲的 channel 则允许存储一定数量的数据项而不阻塞。可以通过指定第二个参数来设置缓冲大小: ```go bufferedCh := make(chan int, 10) ``` 这里创建了一个容量为 10 的带缓冲 channel[^3]。 #### 关闭 Channel 和遍历其值 关闭一个 channel 表明不会再有任何新的值被写入其中。这通常发生在生产者完成所有工作之后。消费者可以通过 range 循环自动处理直到 channel 被关闭为止的所有值: ```go close(ch) for val := range ch { fmt.Println(val) } ``` 注意,在多生产者的场景下应小心管理谁负责关闭 channel,以免引发 panic 错误。 #### Select 语句支持多个通道操作 Go 提供了 select 语句用来等待多个 communication 操作之一变得可用。这是一个典型的非阻塞 I/O 实现方式: ```go select { case msg1 := <-ch1: fmt.Println("received", msg1) case ch2 <- "ping": default: fmt.Println("no activity") } ``` 上述代码展示了如何监听来自不同 channels 的消息或者尝试发送一条新消息给某个 channel。 #### 完整示例:计数器应用 以下展示的是利用 goroutine 和 unbuffered channel 构建的一个简单计数器应用程序: ```go package main import ( "fmt" ) func counter(out chan<- int) { for i := 0; i < 100; i++ { out <- i // 将当前数值传送到 out channel } close(out) // 结束后关闭该 channel } func square(in <-chan int, out chan<- int) { for v := range in { // 遍历输入 channel 的每一个值 out <- v * v // 计算平方并传送至下一个 stage } close(out) // 处理完成后关闭 output channel } func printer(in <-chan int) { for v := range in { // 打印接收到的所有值 fmt.Println(v) } } func main() { naturals := make(chan int) squares := make(chan int) go counter(naturals) go square(naturals, squares) printer(squares) } ``` 在这个程序里,我们启动三个独立的任务——生成自然数序列、计算它们各自的平方以及打印最终的结果集。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玉言心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值