go语言中channel类型

news2024/12/28 18:40:26

目录

什么是channel

为什么要有channel

channel操作使用

初始化:

操作:

单向channel

双向channel,可读可写:

close下什么场景会出现panic

总结


什么是channel

Channels are a typed conduit through which you can send and receive values with the channel operator, <-.

        channel是go语言的核心类型之一,翻译为中文是“通道,管道”,为了实现协程间的同步与通信。遵循FIFO(先进先出)的队列,保证线程安全。

为什么要有channel

“不要用共享内存来通信,而是使用通信来共享内存” -- go语言并发哲学

        任何一种程序语言要实现并发能力,就要做好多线程之间的协调工作,让彼此知道对方状态,获取对方的信息,完成预定任务。大多数的编程语言的并发编程模型是基于线程和内存同步访问控制,go语言的并发编程的模型则用 goroutine 和 channel 来实现。channel是goroutine之间架了一条管道,在管道里传输数据,实现goroutine间的通信来协调工作(当然goroutine实现通信不止channel一种,还有 go语言中协程实现通信的三种方式 context\sync.cond\channel)。

        在go语言中,CSP(Communicating Sequential Processes)模型是go语言并发编程哲学的实现(三种线程模型与CSP实现),goroutine与channel是CSP上层实现的两大基石。

        channel的底层实现保证了协程操作安全:在任何同一时间内,channel中的一个数据只允许一个协程访问,不存在数据竞争。

channel操作使用

初始化

channel是引用类型,有带缓冲channel和无缓冲channel,未初始化的channel值是nil。通过内建函数make (仅对map\slice\channel初始化)分配内存并初始化。

操作

channel只有三种操作方式Send、Receive、close

通过操作符 <- 实现发送或读取数据中文社区更愿意把Send和接收从通信操作符号看chan <- 是发送数据到chan,<- chan是接收chan中数据这是从通信角度理解从读写角度理解我更愿意翻译为chan <- 为写入数据到chan,<-chan为从chan读取数据出来)。数据为go中任意类型

close为关闭chan:close(chan)

虽然go语言采自动垃圾回收机制来管理内存,但go的垃圾回收器不会主动回收运行中的channel, 主动关闭channel为了防止内存泄露。

单向channel

单向channel分为write-only,read-only channel,主要作用有限制通信方向、减少竞态条件、提高代码可读性。

        限制通信方向:使用只写通道可以在某些情况下限制通信的方向,确保特定的协程只能发送数据到通道,而不会在不适当的地方进行接收操作。这有助于清晰地定义协程之间的职责和交互。

        减少竞态条件:当只有一个协程负责向通道发送数据,而其他协程只负责接收时,可以减少竞态条件的出现。这有助于避免数据竞争和复杂的同步问题。

        提高代码可读性:通过使用只写通道,你可以在代码中清楚地表达协程的作用。这有助于其他开发人员更容易地理解代码并阅读文档。

unc worker(id int, jobs <-chan int, results chan<- int) {
   for job := range jobs {
      fmt.Printf("Worker %d started job %d\n", id, job)
      time.Sleep(time.Millisecond)
      fmt.Printf("Worker %d finished job %d\n", id, job)
      results <- job * 2
   }
}

func main() {
   numJobs := 5
   jobs := make(chan int, numJobs)
   results := make(chan int, numJobs)
   // 启动3个工作协程
   for i := 1; i <= 3; i++ {
      go worker(i, jobs, results)
   }
   // 向通道发送任务
   for j := 1; j <= numJobs; j++ {
      jobs <- j
   }
   close(jobs)
   // 收集结果
   for r := 1; r <= numJobs; r++ {
      result := <-results
      fmt.Println("Result:", result)
   }
}

在这个示例中,我们使用只写通道 chan<- 来传递任务给工作协程,工作协程的<- chan只负责从通道中接收任务。这种模式将任务分发和执行解耦,增加了代码的可读性和可维护性。

总之,单向channel在go语言中用于限制通道的使用方向,有助于提高代码的可读性、降低竞态条件和减少复杂性。

双向channel,可读可写:

基本通道使用:创建一个通道,发送数据到通道,然后从通道接收数据

func base() {
   ch := make(chan int) // 创建一个通道
   go func() {
      ch <- 42 // 发送数据到通道
   }()
   value := <-ch                   // 从通道接收数据
   fmt.Println("Received:", value) // Received: 42
}

使用缓冲通道:创建带有缓冲区的通道,可以存储多个数据,然后使用循环向通道发送和接收数据。

func cacheChan() {
   ch := make(chan int, 2) // 创建一个容量为2的缓冲通道
   ch <- 1
   ch <- 2
   value1 := <-ch
   value2 := <-ch
   fmt.Println("Received:", value1, value2) // Received: 1 2
}

协程池: 使用通道来实现一个简单的协程池,从通道中获取任务并分发给协程进行处理。

func worker(id int, jobs <-chan int, results chan<- int) {
   for job := range jobs {
      fmt.Printf("Worker %d started job %d\n", id, job)
      time.Sleep(time.Millisecond)
      fmt.Printf("Worker %d finished job %d\n", id, job)
      results <- job * 2
   }
}

func goroutinePool() {
   numJobs := 5
   numWorkers := 3
   jobs := make(chan int, numJobs)
   results := make(chan int, numJobs)
   
   for i := 1; i <= numWorkers; i++ {
      go worker(i, jobs, results)
   }
   for j := 1; j <= numJobs; j++ {
      jobs <- j
   }
   close(jobs)

   var wg sync.WaitGroup
   wg.Add(numJobs)
   go func() {
      wg.Wait()
      close(results)
   }()

   for r := range results {
      fmt.Println("Result:", r)
      wg.Done()
   }
}

取消协程: 使用通道来实现协程的取消,通过发送信号告知协程停止工作。

func worker(cancel <-chan struct{}) {
   for {
      select {
      case <-cancel:
         fmt.Println("Worker canceled")
         return
      default:
         fmt.Println("Working...")
         time.Sleep(time.Second)
      }
   }
}

func cancelRoutine() {
   cancel := make(chan struct{})
   go worker(cancel)
   time.Sleep(3 * time.Second)
   fmt.Println("Canceling worker...")
   close(cancel)
   time.Sleep(1 * time.Second)
}

close下什么场景会出现panic

在使用channel时,为了获得良好的协程同步与通信结果,在一些场景下会导致程序panic,

如下为读写与channel状态对协程的影响表:

调用close关闭channel时,未初始化时关闭、重复关闭、关闭后发送、发送时关闭,在这四种情况下会出现panic:

        未初始化就关闭

func main() {
   var ch chan int
   close(ch) // panic: close of nil channel
}

        重复关闭

func main() {
   ch := make(chan int)
   close(ch)
   close(ch) // panic: close of closed channel
}

        关闭后发送

func main() {
   wg := sync.WaitGroup{}
   wg.Add(1)
   ch := make(chan int)
   close(ch)

   go func() {
      defer wg.Done()
      ch <- 1 // panic: send on closed channel
   }()
   <-ch
   wg.Wait()
}

        发送后关闭

func main() {
   ch := make(chan int)
   var wg sync.WaitGroup
   wg.Add(1)

   go func() {
      defer wg.Done()
      defer fmt.Println("close ch") // close ch
      defer close(ch)
      go func() {
         ch <- 1 // panic: send on closed channel
      }()
   }()
   fmt.Println(<-ch)
   wg.Wait()
}

不正确地关闭channel会导致程序panic,如何优雅关闭channel参看

参看:《How to Gracefully Close Channels》

总结

        本内容主要讲述了channel的基础知识包括定义使用背景类型操作方式使用场景不正确关闭channelpanic的场景没有对channel的底层实现原理进行解读参看channel的底层实现原理了解

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

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

相关文章

RabbitMQ介绍及常见消息队列

1. RabbitMQ 1.1. 搜索与商品服务的问题 假设我们已经完成了商品详情和搜索系统的开发。我们思考一下&#xff0c;是否存在问题&#xff1f; o 商品的原始数据保存在数据库中&#xff0c;增删改查都在数据库中完成。 o 搜索服务数据来源是索引库&#xff0c;如果数据库商品发…

QT C++实现简易便签

总想着下载一个便签出来自己用&#xff0c;但网上下载的不是功能太多&#xff0c;好多功能都用不到&#xff0c;就是功能太少了&#xff0c;或是界面不太符合自己审美。所以干脆想着自己做一个算了&#xff0c;反正也不是很麻烦。 需求功能&#xff1a; 开机自启动&#xff0c…

无涯教程-PHP - 循环语句

PHP中的循环用于执行相同的代码块指定的次数。 PHP支持以下四种循环类型。 for - 在代码块中循环指定的次数。 while - 如果且只要指定条件为真&#xff0c;就会循环遍历代码块。 do ... while - 循环执行一次代码块&#xf…

Over Permision

文章目录 水平越权垂直越权 如果使用A用户的权限去操作B用户的数据&#xff0c;A的权限小于B的权限&#xff0c;如果能够成功操作&#xff0c;则称之为越权操作。 越权漏洞形成的原因是后台使用了 不合理的权限校验规则导致的。 一般越权漏洞容易出现在权限页面&#xff08;需…

什么是cURL?

cURL无处不在。它几乎隐藏在所有设备中&#xff0c;例如汽车&#xff0c;蓝光播放器等。它通过互联网协议传输任意类型数据。 在本文中&#xff0c;我们将揭开cURL神秘命令行工具的面纱&#xff0c;解释它是如何成为一种通用代码的&#xff0c;并举例说明其用法。 cURL是什么意…

[MAUI]模仿网易云音乐黑胶唱片的交互实现

用过网易云音乐App的同学应该都比较熟悉它播放界面。 这是一个良好的交互设计&#xff0c;留声机的界面隐喻准确地向人们传达产品概念和使用方法&#xff1a;当手指左右滑动时&#xff0c;便模拟了更换唱盘从而导向切换歌曲的交互功能。 今天在 .NET MAUI 中我们来实现这个交互…

地球IT

地球是我们生活的家园&#xff0c;也是人类发展的基地。地球不仅仅是一个行星&#xff0c;更是一个复杂而有机的生态系统。 地球直径约为12,742公里&#xff0c;被称为“蓝色星球”&#xff0c;因为它的表面约70%被水覆盖。海洋是地球上最大的生态系统之一&#xff0c;它们扮演…

商城-学习整理-高级-商城业务-异步线程池(十三)

目录 一、线程1、初始化线程的 4 种方式2、线程池的七大参数3、线程池的运行流程&#xff1a;4、例子5、常见的 4 种线程池6、开发中为什么使用线程池 二、CompletableFuture 异步编排0、业务场景&#xff1a;1、创建异步对象2、计算完成时回调方法3、handle 方法4、线程串行化…

springboot 项目日志配置文件详解

spring boot 项目指定 日志配置文件 在Spring Boot项目中&#xff0c;可以通过在application.properties或application.yml文件中指定日志配置文件来配置日志。 1. 使用application.properties文件&#xff1a; 在application.properties中&#xff0c;您可以使用以下属性来…

系统卡死问题分析

CPU模式 CPU Frequency Scaling (CPUFREQ) Introduction CPU频率调节设备驱动程序的功能。该驱动程序允许在运行过程中更改CPU的时钟频率。一旦CPU频率被更改,必要的电源供应电压也会根据设备树脚本(DTS)中定义的电压值进行变化。通过降低时钟速度,这种方法可以减少功耗…

C语言刷题训练DAY.9

1.线段图案 解题思路&#xff1a; 这里非常简单&#xff0c;我们只需要用一个循环控制打印即可。 解题代码&#xff1a; #include<stdio.h> int main() {int n 0;while ((scanf("%d", &n)) ! EOF){int i 0;for (i 0; i < n; i){printf("*&…

js闭包用法以及和bind的结合使用

bind用法 let info { name: "xuhaitao", age: 36 }function haitao() {console.log(this);}let fun haitao.bind(info)fun();haitao(); 控制台打印: 闭包用法: function xiaoMing() {let v 1;function jia() {v;console.log(v);}function getV() {console.log(…

【C语言】每日一题(单词倒排)

单词倒排&#xff0c;链接奉上。 方法 做题前的预备知识双指针逆序整个逆序单词 做题前的预备知识 在做题时遇到有关判断字母与数字时&#xff0c;因为总会写成str>0&&str<9之类的形式&#xff0c;比较繁琐&#xff0c;而C语言为了解决这个问题&#xff0c;有了…

线段树详解——影子宽度

OK&#xff0c;今天来讲一讲线段树~~ 线段树是什么线段树的实现线段树的时间复杂度线段树的应用线段树的节点结构其他操作和优化例题——影子宽度输入输出格式输入格式输出格式 输入输出样例输入样例输出样例 例题讲解 线段树是什么 线段树&#xff08; S e g m e n t Segmen…

【设计原则】图解何为依赖倒置

依赖倒置原则&#xff08;Dependence Inversion Principle&#xff0c;DIP&#xff09;是指设计代码结构时&#xff0c;高层模块不应该依赖低层模块&#xff0c;二者都应该依赖其抽象。 要理解何为倒置&#xff0c;那就先得明确什么是“正向”&#xff0c;可以看到下图代码是自…

Element Plus <el-table> 组件之展开行Table在项目中使用

目录 官方样式&#xff1a; 展开前&#xff1a; 展开&#xff1a; 原始代码&#xff1a; 代码详解&#xff1a; 项目使用场景&#xff1a; 完成效果&#xff1a; 具体实现范本&#xff1a; 1.调整数据结构 2. 修改标签和数据绑定 3. JavaScript 部分导入和创建对象 …

浅谈日常使用的 Docker 底层原理-三大底座

适合的读者&#xff0c;对Docker有过简单了解的朋友&#xff0c;想要进一步了解Docker容器的朋友。 前言 回想我这两年&#xff0c;一直都是在使用 Docker&#xff0c;看过的视频、拜读过的博客&#xff0c;大都是在介绍 Docker 的由来、使用、优点和发展趋势&#xff0c;但对…

路由跳转--编程式导航

简介 除了使用 创建 a 标签来定义导航链接&#xff0c;我们还可以通过编程式导航实现导航。所谓编程式导航指的是不通过router-link跳转&#xff0c;而是借助 router 的实例&#xff0c;通过代码的方式跳转。 示例&#xff1a; App.vue <template><div id"ap…

正演的数值模拟(零基础,学习中)

摘要: 本贴从零开始学习正演的数值模拟方法. 1. 偏微分基础 引例: 物体从一维坐标的原点开始移动, 在 t t t 时刻, 它在坐标轴的位置由函数 s ( t ) s(t) s(t) 确定, 则速度为位置变化量与时间的比值: v ( t ) d s ( t ) d t lim ⁡ Δ t → 0 s ( t Δ t ) − s ( t )…

HDFS存储魔法解析:在二次元世界中跃动的数据冒险

文章目录 版权声明零 引缘起一 存储原理二 fsck命令2.1 副本块数量的配置2.1.1 全局设置方式2.1.2 临时设置方式 2.2 检查文件的副本数2.3 block大小和复制策略配置 三 NameNode元数据3.1 edits文件3.2 fsimage文件3.3 NameNode元数据管理维护3.4 元数据合并控制参数3.5 Checkp…