Golang之Channel详细介绍

这篇具有很好参考价值的文章主要介绍了Golang之Channel详细介绍。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、概述
通道(Channel)是 Golang 在语言级别上提供的 goroutine 间的通讯方式,可以使用channel在多个 goroutine 之间传递消息。如果说 goroutine 是 Go 程序并发的执行体,channel 就是它们之间的连接。channel 是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。
Golang 的并发模型是 CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明 channel 的时候需要为其指定元素类型。
二、通道的声明
channel是 Go 语言中一种特有的类型。
声明通道类型变量的格式如下:

var 变量名称 chan 元素类型

说明

变量名称:保存通道的变量

chan:声明通道的关键字
元素类型:是指通道中传递元素的类型

注意:chan 类型的空值是 nil,声明后需要配合 make 后才能使用。
示例

var ch1 chan int // 声明一个传递整型的通道

var ch2 chan bool // 声明一个传递布尔型的通道

var ch3 chan []int // 声明一个传递 int 切片的通道

三、创建通道
通道是引用类型,需要使用 make 进行创建。
格式如下:
通道实例 := make(chan 元素类型, [缓冲大小])
复制代码
说明

channel的缓冲大小是可选的。

示例
ch1 := make(chan int,10) // 创建一个能存储10个int类型数据的通道

ch2 := make(chan []int, 3) //创建一个能存储3个[]int 切片类型数据的通道

ch3 := make(chan interface{}) // 创建一个空接口类型的通道, 可以存放任意格式

type Equip struct{ /* 一些字段 */ }
ch4 := make(chan Equip) // 创建Equip指针类型的通道, 可以存放Equip
复制代码
四、channel操作
通道共有发送(send)、接收(receive)和关闭(close)三种操作。
4.1 使用通道发送数据
通道创建后,就可以使用通道进行发送和接收操作。
(1)通道发送数据的格式
通道的发送使用特殊的操作符<-,将数据通过通道发送的格式为:
通道变量 <- 值
复制代码

通道变量:通过make创建好的通道实例。
值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。

示例
func main() {
//创建通道ch
ch := make(chan int,2)
//从通道里面发送数据
ch <- 10 //将10发送到通道ch中
ch <- 20 //将20发送到通道ch中

// 创建一个空接口通道ch1
ch1 := make(chan interface{},2)
ch1 <- 10	// 将10放入通道中
ch1 <- "hello"	// 将hello字符串放入通道中

}
复制代码
(2)发送将持续阻塞直到数据被接收
把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。
Go 程序运行时能智能地发现一些永远无法发送成功的语句并做出提示,代码如下:
func main() {
// 创建一个整型通道
ch := make(chan int)

// 尝试将0通过通道发送
ch <- 0

}
复制代码
运行代码报如下错误
fatal error: all goroutines are asleep - deadlock!
复制代码
报错的意思是:运行时发现所有的 goroutine(包括main)都处于等待 goroutine。也就是说所有 goroutine 中的 channel 并没有形成发送和接收对应的代码。
4.2 使用通道接收数据
通道接收同样使用<-操作符,通道接收有如下特性:

通道的收发操作在不同的两个 goroutine 间进行
由于通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个 goroutine 中进行。

接收将持续阻塞直到发送方发送数据
如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。

每次接收一个元素
通道一次只能接收一个数据元素。

通道的数据接收一共有以下 4 种写法:
(1)阻塞接收数据
阻塞模式接收数据时,将接收变量作为<-操作符的左值,格式如下:
data := <-ch
复制代码
执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。
(2)接收任意数据,忽略接收的数据
阻塞接收数据后,忽略从通道返回的数据,格式如下:
<-ch
复制代码
执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步。
4.3 关闭通道
可以通过调用内置的close函数来关闭通道。
close(ch)
复制代码
**注意:**一个通道值是可以被垃圾回收掉的。通道通常由发送方执行关闭操作,并且只有在接收方明确等待通道关闭的信号时才需要执行关闭操作。它和关闭文件不一样,通常在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。
关闭后的通道有以下特点:

对一个关闭的通道再发送值就会导致 panic。
对一个关闭的通道进行接收会一直获取值直到通道为空。
对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
关闭一个已经关闭的通道会导致 panic。

多返回值模式

当一个通道被关闭后,再往该通道发送值会引发panic,从该通道取值的操作会先取完通道中的值。通道内的值被接收完后再对通道执行接收操作得到的值会一直都是对应元素类型的零值。
对一个通道执行接收操作时支持使用如下多返回值模式。
value, ok := <- ch
复制代码
说明

value:从通道中取出的值,如果通道被关闭则返回对应类型的零值。
ok:通道ch关闭时返回 false,否则返回 true。

示例

定义一个函数循环从通道中接收所有值,直到通道被关闭后退出

func fun(ch chan int) {
for {
value, ok := <-ch
if !ok {
fmt.Println(“通道已关闭”)
break
}
fmt.Printf(“value:%#v,ok:%#v\n”, value, ok)
}
}

func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
fun(ch)
}
复制代码
运行结果
value:1,ok:true
value:2,ok:true
通道已关闭
复制代码
五、通道的容量与长度
在上面介绍的创建通道时,传入的第二个参数表示通道的容量。

当容量为 0 时,说明通道中不能存放数据,在发送数据时,必须要求立马有人接收,否则会报错。此时的通道称之为无缓冲通道。
当容量为 1 时,说明通道只能缓存一个数据,若通道中已有一个数据,此时再往里发送数据,会造成程序阻塞。利用这点可以利用通道来做锁。
当容量大于 1 时,通道中可以存放多个数据,可以用于多个协程之间的通信管道,共享资源。

示例
可以通过 cap 函数和 len 函数获取通道的容量和长度。
func main() {
// 创建一个通道
ch := make(chan int, 3)
fmt.Println(“刚创建成功后:”)
fmt.Printf(“cap = %v,len = %v \n”, cap(ch),len(ch)) //cap = 3,len = 0
ch <- 1
ch <- 2
fmt.Println(“向通道中传入两个参数后:”)
fmt.Printf(“cap = %v,len = %v \n”, cap(ch),len(ch)) //cap = 3,len = 2
<- ch
fmt.Println(“从通道中取出一个值后:”)
fmt.Printf(“cap = %v,len = %v \n”, cap(ch),len(ch)) //cap = 3,len = 1
}
复制代码
六、通道阻塞
6.1 无缓冲通道
如果创建通道的时候没有指定容量,那么可以叫这个通道为无缓冲的通道。无缓冲的通道又称为阻塞的通道。
示例
func main() {
ch := make(chan int)
ch <- 10
fmt.Println(“发送成功”)
}
复制代码
上面这段代码能够通过编译,但是执行的时候会出现以下错误:
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
D:/Codes/gocode/godemo/base10/test04.go:7 +0x31
复制代码
无缓冲通道在通道里无法存储数据,接收端必须先于发送端准备好,以确保你发送完数据后,有人立马接收数据,否则发送端就会造成阻塞,原因很简单,通道中无法存储数据。也就是说发送端和接收端是同步运行的。
6.2 有缓冲通道
在使用make 函数初始化通道的时候为其指定通道的容量。
func main() {
ch := make(chan int, 1) // 创建一个容量为 1 的有缓冲区通道
ch <- 10
fmt.Println(“发送成功”)
}
复制代码
只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的最大数量。
七、 遍历通道
通常会选择使用for range循环从通道中接收值,当通道被关闭后,会在通道内的所有值被接收完毕后会自动退出循环。
示例
func fun2(ch chan int) {
//通过for range遍历通道中的值
for v := range ch {
fmt.Println(v)
}
}

func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
fun2(ch)
}
复制代码
运行结果
1
2
复制代码
八、单向通道
8.1 单向通道概述
Go 语言的类型系统提供了单方向的 channel 类型,顾名思义,单向 channel 只能用于发送或者接收数据。 channel 本身必然是同时支持读写的,否则根本没法用。可以将 channel 隐式转换为单向队列,只收或只发。
单向通道可以分为只读通道和只写通道两种。
8.2 单向通道声明

只读通道

只读通道使用<-chan 表示

var 通道实例 <-chan 元素类型 // 只能接收通道
复制代码
示例
ch1 := make(<-chan int, 2)
//ch1 <- 23
fmt.Println(ch1)
复制代码

只写通道

只写通道使用chan<- 表示

var 通道实例 chan<- 元素类型 // 只能发送通道
复制代码
示例
ch2 := make(chan<- int, 2)
ch2 <- 20
//<- ch2 //error
fmt.Println(ch2)
复制代码
8.3 通道转换
示例
func main() {
c := make(chan int, 3)
//只写通道
var send chan<- int = c // send-only
//只读通道
var recv <-chan int = c // receive-only
send <- 1
// <-send // Error: receive from send-only type chan<- int
<-recv
// recv <- 2 // Error: send to receive-only type <-chan int

//不能将单向 channel 转换为普通 channel
//只写通道转双通道
d := (chan int)(send) // Error: cannot convert type chan<- int to type chan int
//只读通道转双通道
r := (chan int)(recv) // Error: cannot convert type <-chan int to type chan int

}
复制代码
从示例可以看出,不能将单向 channel 转换为普通 channel。
一般在通道的声明时,都不会刻意声明为单通道,这样做会声明一个只进不出,或者只出不进的单通道,没有任何意义。
而是在函数的定义形参过程中指定通道的是发送通道还是接收通道。这样做的目的适用于约束其他代码的行为。
func SendInt(ch chan<- int) {
ch <- rand.Intn(100)
}
复制代码
这个函数只接受一个 chan<- int 类型的参数。在这个函数中的代码只能向参数 ch 发送元素值,而不能从它那里接收元素值。这就起到了约束函数行为的作用。
双向 channel 转化为单向 channel 之间进行转换。
示例
ch := make(chan int)
ch1 := <-chan int(ch) // ch1 是一个单向的读取channel
ch2 := chan<- int(ch) // ch2 是一个单向的写入channel
复制代码
8.4 单向通道使用示例
package main

import (
“fmt”
“math/rand”
“time”
)

// 数据生产者
func producer(header string, channel chan<- string) {
// 无限循环,不停的生产数据
for {
// 将随机数和字符串格式化为字符串发送到通道
channel <- fmt.Sprintf(“%s: %v”, header, rand.Int31())
// 等待1秒
time.Sleep(time.Second)
}
}

// 数据消费者
func consumer(channel <-chan string) {
// 不停的获取数据
for {
// 从通道中取出数据,此处会阻塞直到信道中返回数据
message := <-channel
// 打印数据
fmt.Println(message)
}
}

func main() {
// 创建一个字符串类型的通道
channel := make(chan string)
// 创建producer函数的并发goroutine
go producer(“num1”, channel)
go producer(“num2”, channel)
// 数据消费函数
consumer(channel)
}
复制代码
运行结果
num2: 1298498081
num1: 2019727887
num1: 1427131847
num2: 939984059
num1: 911902081
num2: 1474941318
num2: 336122540
num1: 140954425
复制代码
九、使用示例
9.1 示例一

需求:定义两个方法,一个方法向通道里面写数据,一个向通道里面读取数据。
要求同步进行

说明
1、开启一个 fn1 的协程向通道 inChan 中写入 100 条数据
2、开启一个 fn2 的协程读取 inChan 中写入的数据
3、注意:fn1 和 fn2 同时操作一个通道
4、主线程必须等待操作完成后才可以退出
复制代码
代码
import (
“fmt”
“sync”
“time”
)

/**
goroutine结合Channel使用的简单demo,定义两个方法,一个方法给通道里面写数据,一个给通道里面读取数据。要求同步进行。
*/
var wg sync.WaitGroup

//写数据
func fn1(ch chan int){
for i := 1; i <= 10; i++ {
ch <- i
fmt.Printf(“写入数据\t%v\t成功\n”,i)
time.Sleep(time.Millisecond * 500)
}
close(ch)
wg.Done()
}

//读数据
func fn2(ch chan int){
for v := range ch {
fmt.Printf(“读取数据\t%v\t成功\n”,v)
time.Sleep(time.Millisecond * 10)
}
wg.Done()
}

func main() {
var ch = make(chan int,10)

wg.Add(1)
go fn1(ch)
wg.Add(1)
go fn2(ch)

wg.Wait()
fmt.Println("退出...")

}
复制代码
运行结果
写入数据 1 成功
读取数据 1 成功
读取数据 2 成功
写入数据 2 成功
写入数据 3 成功
读取数据 3 成功
写入数据 4 成功
读取数据 4 成功
写入数据 5 成功
读取数据 5 成功
写入数据 6 成功
读取数据 6 成功
写入数据 7 成功
读取数据 7 成功
写入数据 8 成功
读取数据 8 成功
写入数据 9 成功
读取数据 9 成功
写入数据 10 成功
读取数据 10 成功
退出…
复制代码
9.2 示例二

需求:goroutine 结合 channel 实现统计 1-120000 的数字中哪些是素数?

代码
import (
“fmt”
“sync”
“time”
)

var wg3 sync.WaitGroup

//向 intChan放入 1-120000个数
func putNum(intChan chan int){
for i := 2;i < 120000; i++ {
intChan <- i
}
close(intChan)
wg3.Done()
}

// 从 intChan取出数据,并判断是否为素数,如果是,就把得到的素数放在primeChan
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){
for num := range intChan {
var flag = true
for i := 2; i < num; i++ {
if num % i == 0 {
flag = false
break
}
}
if flag {
primeChan <- num //num是素数
}
}
//要关闭 primeChan
// close(primeChan) //如果一个channel关闭了就没法给这个channel发送数据了
//什么时候关闭primeChan?

//给exitChan里面放入一条数据
exitChan <- true
wg3.Done()

}

//printPrime打印素数的方法
func printPrime(primeChan chan int) {
//for v := range primeChan {
// // fmt.Println(v)
// //}
wg3.Done()
}

func main() {
start := time.Now().Unix()

intChan := make(chan int, 1000)
primeChan := make(chan int, 50000)
exitChan := make(chan bool,16)	//标识primeChan close

//存放数字的协程
wg3.Add(1)
go putNum(intChan)

//统计素数的协程
for i := 0; i < 16; i++ {
	wg3.Add(1)
	go primeNum(intChan,primeChan,exitChan)
}

//打印素数的协程
wg3.Add(1)
go printPrime(primeChan)

//判断exitChan是否存满值
wg3.Add(1)
go func() {
	for i := 0; i < 16; i++ {
		<- exitChan
	}
	//关闭primeChan
	close(primeChan)
	wg3.Done()
}()

wg3.Wait()

end := time.Now().Unix()
fmt.Println("执行完毕...",end - start,"毫秒")

}
复制代码
运行结果
执行完毕… 2 毫秒文章来源地址https://www.toymoban.com/news/detail-659962.html

到了这里,关于Golang之Channel详细介绍的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Golang 并发 Channel的用法

    上面是创建了无缓冲的 channel,一旦有 goroutine 往 channel 发送数据,那么当前的 goroutine 会被阻塞住,直到有其他的 goroutine 消费了 channel 里的数据,才能继续运行。 上面示例中的第二个参数表示 channel 可缓冲数据的容量。只要当前 channel 里的元素总数不大于这个可缓冲容量,

    2024年02月21日
    浏览(49)
  • golang channel执行原理与代码分析

    使用的go版本为 go1.21.2 首先我们写一个简单的chan调度代码 因为ch的数据获取方式有两种,所以这个示例代码写了两次的ch读与写 老样子通过go build -gcflags -S main.go获取到对应的汇编代码 调度make最终被转换为CALL runtime.makechan 调度ch - struct{}{}最终被转换为CALL runtime.chansend1 由于我

    2024年02月05日
    浏览(34)
  • 001 Golang-channel-practice

    最近在练习并发编程。加上最近也在用Golang写代码,所以记录一下练习的题目。   第一道题目是用10个协程打印100条信息,创建10个协程。每个协程都会有自己的编号。每个协程都会被打印10次。   本题的关键是: 创建goroutines :通过 for i := 0; i 10; i++ 循环,创建了10个gorout

    2024年02月02日
    浏览(44)
  • 详解如何在Golang中监听多个channel

    这篇文章主要为大家详细介绍了如何在Golang中实现监听多个channel,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下    我们可以使用select来同时监听多个goroutine。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package main import (   

    2024年02月13日
    浏览(85)
  • 详细介绍golang中.()类型断言的使用方法

    Golang是一门非常流行的编程语言,在很多领域都有着广泛的应用。在开发过程中,很多时候我们需要将函数作为参数传递给其他函数,这时候就需要用到golang中的.()用法。本文将详细介绍golang中.()的使用方法。 在golang中,.()被称为类型断言,可以将一个interface{}类型的变量转

    2024年02月14日
    浏览(50)
  • 【详细介绍分析golang中map的相关知识】

    Golang中的map是一种非常强大和灵活的数据结构,它可以用来存储键值对。在本文中,我们将深入探讨Golang中map的相关知识,包括其定义、初始化、操作以及一些常见的应用场景。 在Golang中,map是一种 引用类型 ,可以使用内置的make函数来创建一个map。map的定义形式为: var

    2024年02月17日
    浏览(43)
  • 004 Golang-channel-practice 左右括号匹配

    第四题 左右括号打印 一个协程负责打印“(”,一个协程负责打印“)”,左右括号的数量要匹配。在这道题目里,我在main函数里进行了一个死循环。会产生一个随机数,随机数就是接下来要打印的左括号的数量。 例如:((((()))))、(())、()。这样是正确的。一个左括号要匹

    2024年02月02日
    浏览(41)
  • 007 Golang-channel-practice 打印水分子

    对应leetcode 1117 https://leetcode.cn/problems/building-h2o/description/ 题目大意:在三个为一组的字符串中,打印两个H,一个O,顺序不限。 这在go里面很容易实现。只需要在每个函数前加上一个go,就可以轻松实现并发了。直接看代码! 打印效果:  

    2024年01月20日
    浏览(38)
  • Golang Channel详解:安全并发通信与避免死锁方法

    深入了解Golang中的Channel,探讨其线程安全性、类型特性以及避免死锁的方法。学习如何正确初始化、存取数据,关闭Channel以及处理只读只写情况。

    2024年02月10日
    浏览(48)
  • Golang 中goroutine、channel 生产环境中例子和应用

    Golang 学习生产环境中例子和应用 大家好,今天我们来聊一聊goroutine、channel产品开发中的应用。如果你还不知道这些是什么,那么恭喜你,你来对地方了!因为我也不知道。 好了,开玩笑了。其实这些都是Go语言中非常重要的概念,尤其是在并发编程中。那么我们来看一下,

    2024年02月10日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包