Golang 并发 Channel的用法

目录

  • Golang 并发 Channel的用法
    • channel 的创建
    • nil channel
      • 读写阻塞示例
      • close示例
    • channel 的读写
    • channel 只读只写
    • 关闭channel
      • channel关闭后,剩余的数据能否取到
      • 读取关闭的channel,将获取零值
      • 使用ok判断,是否关闭
      • 使用for-range退出
      • 使用close(ch)关闭所有下游协程
    • 函数传递引用or值
    • 参考

Golang 并发 Channel的用法

channel 的创建

ch := make(chan int)

上面是创建了无缓冲的 channel,一旦有 goroutine 往 channel 发送数据,那么当前的 goroutine 会被阻塞住,直到有其他的 goroutine 消费了 channel 里的数据,才能继续运行。

ch := make(chan int, 2)

上面示例中的第二个参数表示 channel 可缓冲数据的容量。只要当前 channel 里的元素总数不大于这个可缓冲容量,则当前的 goroutine 就不会被阻塞住。

nil channel

nil是pointers, interfaces, maps, slices, channels 和 function 类型的零值,表示未初始化值。nil不是未定义状态,它本身就是值。error是接口类型,因此error变量可以为nil,但string不能为nil。

下面我们看下nil 通道有什么特点,空通道对操作的反应如下:

  • 从空通道读、写会永远阻塞
  • 关闭通道会终止程序(panic)

空通道是一种特殊通道,总是阻塞。对比非空已关闭的通道仍然可以进行读取,并能够读取对应类型的零值,但对于已关闭的通道发送信息会终止程序。

一般 nil channel 用在 select 上,让 select 不再从这个 channel 里读取数据

读写阻塞示例

示例如下:

func TestNil(t *testing.T) {c := make(chan int)go sendIntegers(c)addIntegers(c)
}func addIntegers(c chan int) {sum := 0t := time.NewTimer(time.Second * 5)for {select {case input := <-c:sum = sum + inputfmt.Println("addIntegers , input : " + strconv.Itoa(input) + " , sum : " + strconv.Itoa(sum))case <-t.C:c = nilfmt.Println("addIntegers , nil channel , sum : " + strconv.Itoa(sum))}}
}func sendIntegers(c chan int) {for {time.Sleep(time.Second * 1)c <- rand.Intn(100)}
}

输出如下

=== RUN   TestNil
addIntegers , input : 81 , sum : 81
addIntegers , input : 87 , sum : 168
addIntegers , input : 47 , sum : 215
addIntegers , input : 59 , sum : 274
addIntegers , nil channel , sum : 274
panic: test timed out after 30s

此示例会一直阻塞下去,addIntegers是程序的主协程会一直阻塞下去,sendIntegers是子协程同样会一直阻塞下去。

其中:输出中的panic是单元测试的Test引发的异常,不需要考虑在内。

close示例

func TestCloseNil(t *testing.T) {c := make(chan int)go writeChannel(c)num := <-cfmt.Println("main goroutine , read num : " + strconv.Itoa(num))c = nilfmt.Println("main goroutine , to close channel .")close(c)time.Sleep(time.Second * 10)}func writeChannel(c chan int) {fmt.Println("writeChannel goroutine ,  running ...")c <- 1
}

输出如下

=== RUN   TestCloseNil
writeChannel goroutine ,  running ...
main goroutine , read num : 1
main goroutine , to close channel .
--- FAIL: TestCloseNil (0.00s)
panic: close of nil channel [recovered]panic: close of nil channel

关闭nil通道会引起程序panic

channel 的读写

写操作

ch := make(chan int)
ch <- 1

读操作

data <- ch

当我们不再使用 channel 的时候,可以对其进行关闭:

 close(ch)

不过读取关闭后的 channel,不会产生 pannic,还是可以读到数据。

如果关闭后的 channel 没有数据可读取时,将得到零值,即对应类型的默认值。

为了能知道当前 channel 是否被关闭,可以使用下面的写法来判断。

 if v, ok := <-ch; !ok {fmt.Println("channel 已关闭,读取不到数据")}

还可以使用下面的写法不断的获取 channel 里的数据:

 for data := range ch {// get data dosomething}

这种用法会在读取完 channel 里的数据后就结束 for 循环,执行后面的代码。

channel 只读只写

在默认情况下,管道是双向的,可读可写,在使用 channel 时我们还可以控制 channel 只读只写操作:

声明为只写,如下:

var chan2 chan<- int
chan2 = make(chan int, 3)
chan2 <- 20

如果试着读此chan,则编译报错,编译错误如下:

invalid operation: cannot receive from send-only channel chan2 (variable of type chan<- int) compiler (InvalidReceive)

声明为只读,不可写,否则编译报错,如下:

var chan3 <-chan int
nm2 := <-chan3

函数可以声明chan只读只写,代码示例:

// 只写操作
func send(ch chan<- int, exitChan chan struct{}) {for i := 0; i < 5; i++ {time.Sleep(time.Second * 1)ch <- i}close(ch)var a struct{}exitChan <- a
}// 只读操作
func recv(ch <-chan int, exitChan chan struct{}) {for {v, ok := <-chif !ok {break}fmt.Println("recv goroutine , value : " + strconv.Itoa(v))}var a struct{}exitChan <- a
}
func TestOnlyReadWrite(t *testing.T) {ch := make(chan int, 10)exitChan := make(chan struct{}, 2)go send(ch, exitChan)go recv(ch, exitChan)var total = 0for _ = range exitChan {total++if total == 2 {break}}fmt.Println("main goroutine , 结束")
}

输出如下:

=== RUN   TestOnlyReadWrite
recv goroutine , value : 0
recv goroutine , value : 1
recv goroutine , value : 2
recv goroutine , value : 3
recv goroutine , value : 4
main goroutine , 结束
--- PASS: TestOnlyReadWrite (5.03s)

关闭channel

channel关闭后,剩余的数据能否取到

golang channel关闭后,其中剩余的数据,是可以继续读取的,channel关闭之后,仍然可以从channel中读取剩余的数据,直到数据全部读取完成。

对于关闭的channel的读写需要注意两点:

  • 如果继续向channel发送数据,会引起panic,
  • 如果继续读数据,得到的是零值(对于int,就是0)。

读取关闭的channel,将获取零值

当读取已关闭的channel时,如果继续读取channel,获取到的是零值,不会堵塞,

另外即使是无缓冲的channel,也将能一直获取到零值。

代码示例如下

func TestCloseDemo01(t *testing.T) {done := make(chan struct{})ch := make(chan int, 3)ch <- 1ch <- 2ch <- 3close(ch)go func() {for {value := <-ch//此处为假设判断,value永远不会等于10if value == 10 {break}fmt.Println("read channel , value : ", value)time.Sleep(time.Second * 1)}done <- struct{}{}}()select {case <-done:fmt.Println("读取channel,正常结束")case <-time.After(time.Second * 5):fmt.Println("超时退出")}
}

输出如下:

=== RUN   TestCloseDemo01
read channel , value :  1
read channel , value :  2
read channel , value :  3
read channel , value :  0
read channel , value :  0
超时退出
--- PASS: TestCloseDemo01 (5.00s)

使用ok判断,是否关闭

读取channel,判断是否关闭:

value, ok := <-ch
  • 当channel关闭时,ok=false
  • 当channel未关闭时,ok=true

通过判断channel是否关闭,当channel关闭时,程序可以正常退出,代码示例如下:

func TestCloseDemo02(t *testing.T) {done := make(chan struct{})ch := make(chan int, 3)ch <- 1ch <- 2ch <- 3close(ch)go func() {for {value, ok := <-chif !ok {break}fmt.Println("read channel , value : ", value)time.Sleep(time.Second * 1)}done <- struct{}{}}()select {case <-done:fmt.Println("读取channel,正常结束")case <-time.After(time.Second * 5):fmt.Println("超时退出")}
}

输出如下:

=== RUN   TestCloseDemo02
read channel , value :  1
read channel , value :  2
read channel , value :  3
读取channel,正常结束
--- PASS: TestCloseDemo02 (3.03s)
PASS

使用for-range退出

for-range是使用频率很高的结构,常用它来遍历数据,range能够感知channel的关闭,当channel被发送数据的协程关闭时,range就会结束,接着退出for循环。

它在并发中的使用场景是:当协程只从1个channel读取数据,然后进行处理,处理后协程退出。

下面这个示例程序,当通道被关闭时,协程可自动退出。

func TestCloseDemo02(t *testing.T) {ch := make(chan int, 3)ch <- 1ch <- 2ch <- 3close(ch)for v := range ch {fmt.Println("value", v)}time.Sleep(time.Second * 10)
}

使用close(ch)关闭所有下游协程

  • 关闭通道,可以主动通知所有协程退出的场景

当启动100个worker时,只要main()执行关闭stopCh,每一个worker都会都到信号,进而关闭。如果main()向stopCh发送100个数据,这种就低效了。

//close关闭所有子协程
func TestCloseDemo04(t *testing.T) {ch := make(chan int, 3)stopCh := make(chan struct{})for i := 1; i < 6; i++ {worker("worker"+strconv.Itoa(i), stopCh, ch)}time.Sleep(time.Second * 5)close(stopCh)time.Sleep(time.Second * 5)
}func worker(workerName string, stopCh <-chan struct{}, ch <-chan int) {go func() {defer fmt.Println(workerName, "goroutine , worker exit")// Using stop channel explicit exitfor {select {case <-stopCh:fmt.Println(workerName, "goroutine , Recv stop signal , return")returndefault:fmt.Println(workerName, "goroutine , worker default ...")}time.Sleep(time.Second * 3)}}()
}

输出如下

=== RUN   TestCloseDemo04
worker5 goroutine , worker default ...
worker3 goroutine , worker default ...
worker4 goroutine , worker default ...
worker1 goroutine , worker default ...
worker2 goroutine , worker default ...
worker3 goroutine , worker default ...
worker2 goroutine , worker default ...
worker5 goroutine , worker default ...
worker4 goroutine , worker default ...
worker1 goroutine , worker default ...
worker4 goroutine , Recv stop signal , return
worker4 goroutine , worker exit
worker2 goroutine , Recv stop signal , return
worker2 goroutine , worker exit
worker5 goroutine , Recv stop signal , return
worker5 goroutine , worker exit
worker1 goroutine , Recv stop signal , return
worker1 goroutine , worker exit
worker3 goroutine , Recv stop signal , return
worker3 goroutine , worker exit
--- PASS: TestCloseDemo04 (10.01s)
PASS

函数传递引用or值

golang 传递给函数chan类型时,是值传递和引用传递?

  • golang默认都是采用值传递,即拷贝传递
  • 有些值天生就是指针(slice、map、channel)

可以看出来map和slice都是指针传递,即函数内部是可以改变参数的值的。而array是数组传递,不管函数内部如何改变参数,都是改变的拷贝值,并未对原值进行处理。

在 Go 语言中,所有的函数参数传递都是值传递(pass by value),当将参数传递给函数时,实际上是将参数的副本传递给函数。然而,这并不意味着在函数内部对参数的修改都不会影响原始数据。因为在 Go 中,有些数据类型本身就是引用类型,比如切片(slice)、映射(map)、通道(channel)、接口(interface)和指针(pointer)。当这些类型作为参数传递给函数时,虽然传递的是值,但值本身就是一个引用。

小结
Go 语言中的参数传递总是值传递,意味着传递的总是变量的副本,无论是基本数据类型还是复合数据类型。由于复合数据类型(如切片、映射、通道、接口和指针)内部包含的是对数据的引用,所以在函数内部对这些参数的修改可能会影响到原始数据。理解这一点对于编写正确和高效的Go代码至关重要。

另外即使是引用类型,比如切片,当长度或容量(比如使用 append 函数)发生变化了,可能会导致分配新的底层数组。这种情况下,原始切片不会指向新的数组,但是函数内部的切片会。因此,如果想在函数内部修改切片的长度或容量并反映到外部,应该传递一个指向切片的指针。

参考

  • https://www.cnblogs.com/-wenli/p/12350181.html
  • https://segmentfault.com/a/1190000017958702
  • https://zhuanlan.zhihu.com/p/395278270
  • https://zhuanlan.zhihu.com/p/613771870
  • Go里面如何实现广播 https://juejin.cn/post/6844903857395335182

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/692093.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

debug - 只要在内存中有显示相关的数据, 就会被CE找到

文章目录 debug - 只要在内存中有显示相关的实际数据, 就会被CE找到概述笔记demo实现demo运行效果用CE查找实际数据地址找到自己的调试点 - 方法1找到自己的调试点 - 方法2打补丁备注END debug - 只要在内存中有显示相关的实际数据, 就会被CE找到 概述 自己写了一个demo, 想验…

哈尔滨酒店为什么要进行神秘顾客检查

神秘顾客调研是一种非常有效市场调研方法&#xff0c;通过第三方人员以普通消费者的身份对特定企业或服务进行评估和反馈。这些“神秘顾客”会接受详细的培训&#xff0c;了解如何评估服务质量、产品特性、员工表现等方面。在访问过程中&#xff0c;他们会记录自己的观察和体验…

人工智能_PIP3安装使用国内镜像源_安装GIT_普通服务器CPU_安装清华开源人工智能AI大模型ChatGlm-6B_002---人工智能工作笔记0097

接着上一节来看,可以看到,这里 创建软连接以后 [root@localhost Python-3.10.8]# ln -s /usr/local/python3/bin/python3 /usr/bin/python3 [root@localhost Python-3.10.8]# python3 -V Python 3.10.8 [root@localhost Python-3.10.8]# pwd /usr/local/Python-3.10.8 [root@…

Vue26 内置标签 v-text v-html

实例 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>v-text指令</title><!-- 引入Vue --><script type"text/javascript" src"../js/vue.js"></script></head><…

第一件事 什么是 Java 虚拟机 (JVM)

1、什么是虚拟机&#xff1f; - 这个其实是一个挺逗的事情&#xff0c;说白了&#xff0c;就是基于某个硬件架构&#xff0c;在这个硬件部署了一个操作系统&#xff0c;再构架一层虚拟的操作系统&#xff0c;这个新构架的操作系统就是虚拟机。 不知道的兄弟姐妹们&#xff0c;…

1629: 【动态规划】【背包】完全背包(优化)

题目描述 设有N种物品&#xff0c;每种物品有一个重量及一个价值。但每种物品的数量是无限的&#xff0c;同时有一个背包&#xff0c;最大载重量为M&#xff0c;今从N种物品中选取若干件(同一种物品可以多次选取)&#xff0c;使其重量的和小于等于M&#xff0c;而价值的和为最…

重学Java 18.学生管理系统项目

臣无祖母&#xff0c;无以至今日&#xff0c;祖母无臣&#xff0c;无以终余年 母孙二人&#xff0c;更相为命&#xff0c;是以区区不能废远 —— 陈情表.李密 —— 24.2.20 一、编写JavaBean public class Student {//学号private int id;//姓名private String name;//年龄pr…

Codeforces Round 925 (Div. 3)(A,B,C,D,E,F,G)

比赛链接 这场打的很顺&#xff0c;感觉难度和 div 4 差不多&#xff0c;不是很难。D题稍微考了考同余的性质&#xff0c;E题直接模拟过程即可&#xff0c;F题也可以暴力模拟或者拓扑排序&#xff0c;G题是个数学题&#xff0c;是个简单隔板法。A到F题都可以直接模拟就有点离谱…

云计算计算资源池与存储池访问逻辑

在云计算环境中&#xff0c;计算资源池和存储池通常是分开管理和访问的。计算资源池包含了用于运行虚拟机的 CPU、内存等计算资源&#xff0c;而存储池则提供了用于存储虚拟机镜像、数据等的存储资源。 计算资源池和存储池之间通常通过网络进行访问&#xff0c;它们之间不存在直…

解析DApp的延展性:深度解析与未来展望

每天五分钟讲解一个电商模式&#xff0c;大家好我是模式策划啊浩Zeropan_HH。 随着区块链技术的不断演进&#xff0c;去中心化应用&#xff08;DApp&#xff09;已成为数字革命的前沿阵地。作为区块链技术的核心应用之一&#xff0c;DApp的延展性不仅关乎其性能和用户体验&…

「WinCC报警系统专题」简述“消息系统”

WinCC通过报警给操作员提供了有关过程故障和错误的信息。它们有助于尽早检测重要情况和避免停机时间。 一、消息系统 消息&#xff08;报警&#xff09;系统由组态和运行系统组件组成。 1、组态系统 报警记录编辑器&#xff08;如图1所示&#xff09;是报警系统的组态组件。报…

Java,SpringBoot中导出excel文件

依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml<…

docker (九)-进阶篇-docker-compos最佳实践部署zabbix

一 部署docker环境 关闭防火墙、selinux、开启docker&#xff0c;并设置开机自启动 注意点&#xff1a;docker部署的时候&#xff0c;bip要指定&#xff0c;不然会导致虚拟机ip和容器ip冲突&#xff0c;ssh连不上虚拟机 部署请参考 docker &#xff08;二&#xff09;-yum…

为什么程序员不能一次性写好,需要不停改bug?

写程序不是一次性完成的原因有很多&#xff0c;其中包括了解不充分、需求变更、复杂性、人为因素等多个方面的原因。 需求不明确&#xff1a; 在项目一开始&#xff0c;对需求可能存在歧义或不完整的理解。有时候&#xff0c;业务需求会在开发过程中发生变化&#xff0c;导致…

route命令学习总结

route命令学习总结 参考链接: 1、route指令使用详解 https://blog.csdn.net/justlpf/article/details/1290452842、route命令详解 https://www.kancloud.cn/chunyu/php_basic_knowledge/2106519 route命令用于显示和操作IP静态路由表。用于跨网段之间通信 route命令主要用于…

前端登录随机数字字母组合验证

背景&#xff1a;系统登录页面只有用户名密码一种校验方式&#xff0c;没有验证码&#xff0c;可能导致暴力破解。 实现逻辑&#xff1a; <el-form-item prop"code"><el-inputv-model"loginForm.captchaCode"auto-complete"off"placeho…

linux 测试网络速率

1. ethtool ethtool是很强大的查询网卡&#xff08;嵌入式称为phy芯片&#xff09;配置的工具&#xff0c;几乎phy芯片芯片手册寄存器能配置的选项&#xff0c;ethtool都能查询到&#xff1b;嵌入式调试phy芯片的时候经常用到该命令&#xff1b;最简单的指令如下 ethtool eth0…

函数——递归3(c++)

求10097……41的值 问题描述 求 10097 ⋯ 41 的值。 输入 无。 输出 输出一行&#xff0c;即求到的和。 #include <bits/stdc.h> using namespace std; int aaa(int,int,int); int main() {cout<<aaa(1,100,0);return 0; } int aaa(int i,int n,int sum) {if…

Pandas 合并DataFrame中一行的所有字符串

import pandas as pd df pd.DataFrame({ col1: [Hello, World, ], col2: [from, of, the], col3: [Pandas, Data, World], col4: [!, !, !] }) # 自定义一个函数来合并一行的所有字符串&#xff0c;接收一行数据作为输入 def merge_row_strings(row): # 使用join方…

YOLOV8改进系列指南

基于Ultralytics的YOLOV8改进项目.(69.9) 为了感谢各位对V8项目的支持,本项目的赠品是yolov5-PAGCP通道剪枝算法.具体使用教程 专栏改进汇总 二次创新系列 ultralytics/cfg/models/v8/yolov8-RevCol.yaml 使用(ICLR2023)Reversible Column Networks对yolov8主干进行重设计,里…