channel

为什么需要channel

使用全局变量加锁同步来解决goroutine的竞争,可以但不完美

  • 难以精确控制等待时间​(主线程无法准确知道所有 goroutine 何时完成)。

  • 全局变量容易引发竞态条件​(即使加锁,代码复杂度也会增加)。

  • 不够优雅,Go 更推荐使用 ​channel​ 进行通信。

channel基本介绍

  • ​channel(通道)​​ 是一种用于 ​goroutine(协程)之间通信和同步​ 的机制。
  • channel的本质是一个队列,遵循先进先出(FIFO)
  • channel是有类型的,一种channel只能存储类型与该channel的类型相同的数据
  • channel是线程安全的,不需要加锁
  • channel是引用类型,必须初始化make后才能写入数据,未初始化的channel是nil

channel快速入门

channel的声明

var 变量名 chan 数据类型

channel的初始化

var myChan chan int = make(chan int, 2)

发送数据到channel

myChan <- 10
myChan <- 20

此处注意:channel不像map等会自动扩容,channel接收的数据数的最大值在make函数里已经自定义完成了,容量就是一直这么大,不会改变。

从channel接收数据

num := <-myChan
fmt.Println(num)

输出结果:10
此处接收数据不能超出myChan现有的数据量,也就是myChan的长度。

channel的细节

  1. channel中只能存放指定的数据类型

  2. channle的数据放满后,就不能再放入了

  3. channel放满后,如果从channel取出数据后,可以继续放入

<-myChan也是取出了数据,只是没有被接收罢了

  1. 在没有使用协程的情况下,如果channel数据取完了,再取,就会报deadlock
易错点

如果想在channel中输入多样的数据类型,就将channel声明成空接口interface{}的类型
代码示例:
func main() {
myChan := make(chan interface{}, 3)
myChan <- 10
myChan <- “sa”
person := Person{“xxx”}
myChan <- person
<-myChan
<-myChan
person2 := <-myChan
// fmt.Printf(“person2的type%T,值%v,name%v”, person2, person2, person2.Name)
person3 := person2.(Person)
fmt.Printf(“person3的type:%T,值:%v,name:%v”, person3, person3, person3.Name)
}
输出结果:person3的typemain.Person,值{xxx},namexxx

唉,为什么要person3 := person2.(Person)呢,直接用fmt.Printf(“person2的type%T,值%v,name%v”, person2, person2, person2.Name)不好吗?
当然不好啦,使用这个代码会报错,会说person2.Name undefined
为什么呢?
因为person2是从channel中读取的interface{}类型,虽然实际值是Person类型,但编译器不知道其具体类型,因此无法直接访问Name字段。
所以要通过person3 := person2.(Person),提取一个类型断言后的值,将其转换为具体的Person类型,然后才能访问其字段

channel的关闭

发送方可以关闭 channel,表示不再发送数据
内置函数:close(ch)
•​关闭后,仍然可以接收数据​(直到 channel 为空)。
•​向已关闭的 channel 发送数据会 panic。

channel的遍历

  1. 通过 for-range 遍历
    代码示例:
    func main() {
    myChan := make(chan int, 3)
    myChan <- 10
    myChan <- 30
    myChan <- 20
    close(myChan)
    for v := range myChan {
    fmt.Printf(“%v\n”, v)
    }
    }
    输出结果:
    10
    30
    20

for range会一直从 ch接收数据,直到 ch被关闭。
如果 ch未关闭,for range会一直阻塞,可能导致死锁。

手动检查 channel 是否关闭
可以用 value, ok := <-ch的方式检查 channel 是否关闭, 如果 channel 关闭,ok 为 false

  1. 传统for循环
    func main() {
    myChan := make(chan int, 3)
    myChan <- 10
    myChan <- 30
    myChan <- 20
    len := len(myChan)
    for i := 0; i < len; i++ {
    fmt.Println(<-myChan)
    }
    }

也可以正常有序输出,输出结果与for-range一致

channel的阻塞

阻塞是指 goroutine 在 channel 操作上等待,但不会导致整个程序卡死。​

  1. 从空的 channel 接收数据
  2. 向已满的缓冲 channel 发送数据
  3. 读比写的操作慢,导致出现(2)情况
  4. 写比读的操作慢,导致出现(1)情况

channel的死锁

死锁是指所有 goroutine 都在等待对方释放资源,导致程序无法继续执行。

  1. 所有 goroutine 都在等待 channel
  2. 未关闭 channel 导致 for range死锁

使用细节

  1. channel可以声明为只读,或者只写性质

此处只读只写只是一种属性,并不会改变channel的类型,该是chan int 就还是chan int
chan<- int 是只写
<-chan int 是只读

代码示例:
package main

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

// 只写通道:用于发送订单
func orderProducer(orderChan chan<- int, doneChan chan<- struct{}) {
defer close(orderChan) // 生产结束后关闭订单通道

for i := 1; i <= 5; i++ {orderID := rand.Intn(1000) + 1000 // 模拟生成订单号fmt.Printf("📦 生成订单 #%d (ID: %d)\n", i, orderID)orderChan <- orderIDtime.Sleep(time.Second) // 模拟生产间隔
}doneChan <- struct{}{} // 发送完成信号

}

// 只读通道:用于处理订单
func orderProcessor(orderChan <-chan int, doneChan chan<- struct{}) {
for orderID := range orderChan { // 自动检测通道关闭
processTime := time.Duration(rand.Intn(1500)) * time.Millisecond
fmt.Printf(“处理订单 ID: %d (耗时: %v)\n”, orderID, processTime)
time.Sleep(processTime)
}

doneChan <- struct{}{} // 发送完成信号

}

func main() {
// 初始化通道(带缓冲)
orderChan := make(chan int, 3) // 订单通道(缓冲3个订单)
doneChan := make(chan struct{}, 2) // 控制通道(缓冲2个信号)

// 启动服务
go orderProducer(orderChan, doneChan) // 订单生产(只写)
go orderProcessor(orderChan, doneChan) // 订单处理(只读)// 等待两个服务完成
for i := 0; i < 2; i++ {<-doneChan
}
fmt.Println("所有订单处理完成")

}

这段代码中,main函数中定义的orderChan是一个chan int 类型,但他可以同时被使用在只读和只写的函数里,这就很大程度上的便于代码的管理,防止误操作。

  1. select解决 channel 阻塞问题
    日常中,难以准确判断读取/写入与关闭时机难以掌握,所以提出select,虽然select还是无法关闭channel,但是能防止防止读取/写入时的无限等待
    代码示例
    for{
    select {
    case msg := <-ch1:
    fmt.Println(“收到 ch1:”, msg)
    case msg := <-ch2:
    fmt.Println(“收到 ch2:”, msg)
    case <-time.After(3 * time.Second): // 超时控制
    fmt.Println(“读取超时”)
    return
    }
    }

如果多个 case 的 channel 同时就绪(例如多个 channel 都有数据可读),select​会随机选择一个执行​(公平调度,避免饥饿问题)
select​自动忽略未就绪的 channel​(无论是否关闭),无需手动处理。
此处的ch1哪怕没有关闭,也不会报错,而是在无法从ch1取到值后,会暂时将这个case不考虑在执行case内
​每次执行 select时都会重新检查所有 case的就绪状态
还有,最后的return不能使用break代替,因为break只能退出select不能退出for循环,所以相当于重新开始了
return其实还可以用之前提到的label来代替,就是给这个for循环一个标签,然后break label就好了 (但是这种方式并不建议,可读性较差)

  1. recover来防止出现因为一个线程的错误导致其它线程无法进行
    原错误代码:
    package main

import (
“fmt”
“time”
)

// 1. 循环打印 “hello,world”
func sayHello() {
for i := 0; i < 10; i++ {
fmt.Println(“hello,world”)
time.Sleep(1 * time.Second)
}
}

// 2. 测试未初始化的 map(会触发 panic)
func test() {
var myMap map[int]string
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
myMap[0] = “golang” // error: 未初始化的 map 赋值会导致 panic
}

// 3. 主函数(并发执行)
func main() {
go sayHello() // 启动协程
go test() // 启动协程(会崩溃)

// 主线程继续执行
for i := 0; i < 10; i++ {fmt.Printf("main() ok=%d\n", i)time.Sleep(1 * time.Second)
}

}
输出结果:
main() ok=0
hello,world
panic: assignment to entry in nil map

报了panic错误,主线程并没有正常运行

修改代码:
func test() {
var myMap map[int]string
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
myMap[0] = “golang” // error: 未初始化的 map 赋值会导致 panic
}

将错误的test函数加上错误捕获,异常处理

输出结果:
hello,world
assignment to entry in nil map
main() ok=0
main() ok=1
hello,world
hello,world
main() ok=2
hello,world
main() ok=3
hello,world
main() ok=4
hello,world
main() ok=5
main() ok=6
hello,world
hello,world
main() ok=7
main() ok=8
hello,world
hello,world
main() ok=9

即使仍是错误,也依旧不影响其它线程

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

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

相关文章

苹果XR芯片介绍

苹果的 XR 芯片技术主要体现在 A 系列、M 系列处理器以及专为空间计算设计的 R1 协处理器中。以下从技术架构、产品迭代和综合对比三个维度展开分析&#xff1a;一、技术架构解析1. A 系列芯片&#xff08;以 A12 Bionic 为例&#xff09;制程工艺&#xff1a;7nm&#xff08;台…

达梦数据库巡检常用SQL(三)

达梦数据库巡检常用SQL(三) 数据库SQL运行检查 数据库SQL运行检查 死锁的事务情况: SELECT TO_CHAR(HAPPEN_TIME,YYYY-MM-DD HH24:MI:SS) HAPPEN_TIME,SQL_TEXT FROM V$DEADLOCK_HISTORY WHERE HAPPEN_TIME >DATEADD(DAY,-30,

基于SpringBoot的校园周边美食探索及分享平台

1. 项目简介 项目名称&#xff1a;校园周边美食探索及分享平台 项目背景&#xff1a;针对校园师生对周边美食信息的需求&#xff0c;构建一个集美食推荐、鉴赏、评论互动及社交功能于一体的平台&#xff0c;帮助用户发现优质美食资源并进行分享交流。 主要目标&#xff1a; 提供…

Go数据结构与算法-常见的排序算法

虽然看过别人写了很多遍&#xff0c;而且自己也写过很多遍&#xff08;指的是笔记&#xff09;&#xff0c;但是还是要写的就是排序算法。毕竟是初学Go语言&#xff0c;虽然之前写过&#xff0c;但是还是打算再写一遍。主要包括插入排序、选择排序、冒泡排序、快速排序、堆排序…

第 6 篇:目标规则与负载均衡 - `DestinationRule` 详解

系列文章:《Istio 服务网格详解》 第 6 篇:目标规则与负载均衡 - DestinationRule 详解 本篇焦点: 深入理解 DestinationRule 的核心作用:定义流量在到达目的地之后的行为。 详细剖析其三大核心功能:服务子集 (Subsets), 流量策略 (Traffic Policy), TLS 设置。 动手实战…

一个简洁的 C++ 日志模块实现

一个简洁的 C 日志模块实现 1. 引言 日志功能在软件开发中扮演着至关重要的角色&#xff0c;它帮助开发者追踪程序执行过程、诊断问题以及监控系统运行状态。本文介绍一个使用 C 实现的轻量级日志模块&#xff0c;该模块支持多日志级别、线程安全&#xff0c;并提供了简洁易用…

C语言---数据类型

文章目录数据类型分类1. 基本类型 (Basic Types)a. 整数类型 (Integer Types)char (字符型)int (整型)short (短整型)long (长整型)long long (C99标准引入)图片汇总b. 浮点类型 (Floating-Point Types)float (单精度浮点型)double (双精度浮点型)long double (长双精度浮点型)…

本搭建乌云漏洞库

1.下载镜像站文件&#xff0c;并拖入虚拟机 2.将bugs.rar解压至网站根目录下 /var/www/html 3.配置bugs/conn.php 4.在bugs下创建upload目录&#xff0c;将10-14、15-a、15-b、16压缩包文件解压到该upload目录 5.把wooyun.rar解压到 /mysql/data/wooyun目录下 6.配置hosts文件后…

Vmware虚拟机 处理器配置选项配置介绍

1. 处理器配置选项好&#x1f44c;&#xff0c;我来帮你逐一解读 VMware 里 虚拟机处理器 这些选项的含义。 你截的图里&#xff0c;主要有三块内容&#xff1a; 处理器数量 每个处理器的内核数量 ©虚拟化引擎1️⃣ 处理器数量 这是分配给虚拟机的 逻辑 CPU 插槽数。一般…

day40-tomcat

1.每日复盘与今日内容1.1复盘keepalived高可用配置抢占式与非抢占式脑裂keepalived处理Nginx挂掉1.2今日内容部署、安装、配置tomcat(systemctl)Tomcat主配置文件部署静态页部署zrlog&#x1f35f;&#x1f35f;&#x1f35f;&#x1f35f;&#x1f35f;接入负载均衡挂载到NFS2…

【RA-Eco-RA4E2-64PIN-V1.0 开发板】步进电机的串口控制

【RA-Eco-RA4E2-64PIN-V1.0 开发板】步进电机的串口控制 本文介绍了 RA-Eco-RA4E2-64PIN-V1.0 开发板通过串口指令实现 28BYJ-48 步进电机旋转角度和速度的精确控制的项目设计。 项目介绍 硬件连接&#xff1a;28BYJ-48 步进电机、ULN2003 驱动板、Jlink 调试器、供电电源等&am…

PiscCode基于 Mediapipe 的人体多模态关键点检测与可视化系统 —— HumanMultiLandmarker 深度解析

一、引言 在计算机视觉领域&#xff0c;人体关键点检测&#xff08;Human Pose Estimation&#xff0c;HPE&#xff09;一直是研究和应用的热点方向之一。随着深度学习与实时图像处理技术的发展&#xff0c;人体姿势估计已经从传统的 2D 检测走向了 3D 空间建模&#xff0c;并…

文献阅读笔记【物理信息机器学习】:Physics-informed machine learning

文献阅读笔记&#xff1a;Physics-informed machine learningSummaryResearch ObjectiveBackground / Problem Statement问题背景研究现状需解决的问题问题出现的原因分析问题解决思路Method(s)问题建模作者解决问题的方法/算法1. 观测偏差&#xff08;Observational Biases&am…

Linux服务环境搭建指南

实验拓扑概述**实验拓扑&#xff1a; APPSRV&#xff1a; 主机名&#xff1a;appsrv.example.com ip地址&#xff1a;192.168.100.10 网关&#xff1a;192.168.100.254 网卡为NAT模式 STORAGESRV&#xff1a; 主机名&#xff1a;storagesrv.example.com ip地址&#xff1a;192.…

[特殊字符] 数据库知识点总结(SQL Server 方向)

一、数据库基础概念数据库&#xff08;Database&#xff09;&#xff1a;存储和管理数据的容器。数据表&#xff08;Table&#xff09;&#xff1a;以行和列形式组织数据。行&#xff08;Row&#xff09;&#xff1a;一条记录。列&#xff08;Column&#xff09;&#xff1a;字…

【PSINS工具箱】MATLAB例程,二维平面上的组合导航,EKF融合速度、位置和IMU数据,4维观测量

文章目录关于工具箱程序简介代码概述核心功能与步骤运行结果MATLAB代码关于工具箱 本文所述的代码需要基于PSINS工具箱&#xff0c;工具箱的讲解&#xff1a; PSINS初学指导&#xff1a;https://blog.csdn.net/callmeup/article/details/137087932 本文为二维平面上的定位&am…

MiMo-VL 技术报告

摘要 我们开源了 MiMo-VL-7B-SFT 和 MiMo-VL-7B-RL 两个强大的视觉语言模型,它们在通用视觉理解和多模态推理方面均展现出最先进的性能。MiMo-VL-7B-RL 在 40 项评估任务中的 35 项上优于 Qwen2.5-VL-7B,并在 OlympiadBench 上获得 59.4 分,超越了参数量高达 780 亿的模型。…

CTFshow Pwn入门 - pwn 19

先看main函数&#xff1a;fclose(_bss_start) fclose(stdout) 关闭了默认fd1的输出&#xff0c;所以system的结果无法直接看到。 思路&#xff1a; 输出重定向。 ls 1>&0 ls >&0 ls >&2 ###三种写法均可将输出重定向到能回显的终端并获得一个新的交互…

Redis(以Django为例,含具体操作步骤)

简介Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据结构存储系统&#xff0c;支持多种数据结构&#xff08;如字符串、哈希、列表、集合、有序集合等&#xff09;&#xff0c;可用作数据库、缓存或消息队列。其核心特点包括&#xff1a;高性能&am…

浏览器解析网址的过程

问题浏览器解析网址的过程我的回答当你在浏览器地址栏输入一个URL&#xff08;比如www.example.com&#xff09;并按下回车后&#xff0c;会发生以下一系列步骤&#xff1a;首先&#xff0c;浏览器会解析URL结构&#xff0c;确定要访问的协议、域名和路径。如果你没有输入协议部…