第 16 章 - Go语言 通道(Channel)

在Go语言中,channel 是一个非常重要的概念,它主要用于协程之间的通信。通过 channel,你可以安全地传递数据从一个协程到另一个协程,而不需要担心并发控制的问题。下面我们将详细介绍 channel 的不同类型及其使用方法,并通过具体例子来加深理解。

Channel 的定义和使用

在 Go 中,创建一个 channel 非常简单,可以使用内置的 make 函数。基本语法如下:

ch := make(chan int)

这条语句创建了一个传输整型数据的 channel。如果没有指定缓冲区大小,那么这个 channel 就是一个无缓冲 channel。

无缓冲通道

无缓冲通道是最简单的形式,当一个值被发送到一个无缓冲通道时,发送操作会阻塞直到有接收者准备好接收这个值。同样,如果尝试从一个无缓冲通道接收值,接收操作也会阻塞,直到有发送者发送一个值。

示例:

package main

import "fmt"

func main() {
    ch := make(chan int) // 创建一个无缓冲通道

    go func() { // 启动一个 goroutine 发送数据
        ch <- 42 // 阻塞,直到有人接收
    }()

    fmt.Println(<-ch) // 阻塞,直到有人发送
}

在这个例子中,我们创建了一个无缓冲的 int 类型 channel,并启动了一个新的 goroutine 来发送数字 42 到这个 channel。主函数中的 <-ch 操作会阻塞,直到接收到值。

有缓冲通道

与无缓冲通道不同,有缓冲通道允许在没有接收者的情况下存储多个值。创建带缓冲区的 channel 时,需要指定缓冲区的大小。如果缓冲区未满,发送操作不会阻塞;只有当缓冲区已满时,发送操作才会阻塞,直到有接收者取出一个值。

示例:

package main

import "fmt"

func main() {
    ch := make(chan int, 2) // 创建一个带缓冲区的通道,缓冲区大小为2

    ch <- 1 // 不会阻塞
    ch <- 2 // 不会阻塞

    go func() {
        fmt.Println(<-ch) // 输出: 1
        fmt.Println(<-ch) // 输出: 2
    }()

    // 主goroutine继续执行,因为已经有两个值在缓冲区内
    fmt.Println("Main function continues")
}

在这个例子中,我们创建了一个缓冲区大小为2的 int 类型 channel。我们可以连续两次向该 channel 发送值而不被阻塞,因为缓冲区有足够的空间来存储这些值。

结合案例

假设我们需要实现一个简单的生产者-消费者模型,其中生产者将一系列数字发送到一个 channel,而消费者从同一个 channel 接收并处理这些数字。

生产者-消费者模型:

package main

import (
    "fmt"
    "time"
)

func producer(ch chan<- int) {
    for i := 1; i <= 5; i++ {
        ch <- i // 发送数字
        time.Sleep(time.Second) // 模拟延迟
    }
    close(ch) // 关闭通道,表示没有更多的值要发送
}

func consumer(ch <-chan int) {
    for num := range ch {
        fmt.Printf("Received number: %d\n", num)
        time.Sleep(2 * time.Second) // 模拟处理时间
    }
}

func main() {
    ch := make(chan int, 3) // 创建一个带缓冲区的通道

    go producer(ch) // 启动生产者
    go consumer(ch) // 启动消费者

    time.Sleep(10 * time.Second) // 让main goroutine等待足够长的时间以确保所有任务完成
}

在这个例子中,producer 函数作为一个生产者,不断地向 channel 发送数字;consumer 函数作为一个消费者,从 channel 接收数字并打印出来。我们使用了带缓冲区的 channel 来减少生产者和消费者之间的阻塞情况,从而提高程序的效率。

以上就是关于 Go 语言中 channel 的基本介绍和使用案例。希望这些信息能帮助你更好地理解和应用 channel

当然可以,接下来我们将进一步探讨 channel 的一些高级用法,包括多路复用、选择器 select、同步和关闭通道等。

多路复用

在 Go 语言中,可以使用 select 语句来监听多个 channel 的通信状态。这使得程序能够同时处理多个 channel 上的数据流,而不需要为每个 channel 单独编写循环或使用多个 goroutine。

示例:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)

	go func() {
		time.Sleep(2 * time.Second)
		ch1 <- "from ch1"
	}()

	go func() {
		time.Sleep(1 * time.Second)
		ch2 <- "from ch2"
	}()

	for i := 0; i < 2; i++ {
		select {
		case msg1 := <-ch1:
			fmt.Println(msg1)
		case msg2 := <-ch2:
			fmt.Println(msg2)
		}
	}
}

在这个例子中,我们创建了两个带有延迟发送消息的 channel,并通过 select 语句监听这两个 channelselect 会随机选择一个已经准备好的 case 执行,这样可以有效地处理并发情况下的多路复用。

同步

channel 可以用于同步 goroutine 的执行。例如,可以在一个 goroutine 完成其工作后通过 channel 发送一个信号给其他 goroutine。

示例:

package main

import (
	"fmt"
	"time"
)

func worker(done chan bool) {
	fmt.Println("Working...")
	time.Sleep(time.Second)
	fmt.Println("Done")

	done <- true // 工作完成后发送信号
}

func main() {
	done := make(chan bool, 1)
	go worker(done)

	<-done // 等待工作完成的信号
	fmt.Println("Worker finished.")
}

这里,worker 函数在完成工作后会通过 done channel 发送一个信号,主函数则通过 <-done 阻塞等待这个信号,从而实现了同步。

关闭通道

当不再向 channel 发送数据时,应该关闭该 channel。关闭 channel 后,不能再向其发送数据,但是仍然可以从已关闭的 channel 中接收数据,直到所有数据都被读取完毕。

示例:

package main

import "fmt"

func produce(ch chan<- int) {
	for i := 1; i <= 5; i++ {
		ch <- i
	}
	close(ch) // 关闭通道
}

func consume(ch <-chan int) {
	for v := range ch {
		fmt.Println(v)
	}
}

func main() {
	ch := make(chan int)
	go produce(ch)
	consume(ch)
}

在这个例子中,produce 函数在发送完所有数据后关闭了 ch channel。consume 函数使用 for v := range ch 循环来接收数据,直到 ch 被关闭并且所有数据都已被读取。

错误处理

在处理 channel 时,可能会遇到错误,如从已关闭的 channel 接收数据。Go 提供了一种优雅的方式来处理这种情况,即通过多值赋值来检查是否从 channel 接收到了值:

v, ok := <-ch
if !ok {
    // channel 已经关闭且没有更多数据可读
}

总结

通过上述示例,我们了解了如何使用 channel 进行多路复用、同步和关闭操作。channel 是 Go 语言中非常强大的特性,它不仅简化了并发编程,还提高了代码的可读性和可靠性。掌握 channel 的使用对于编写高效的 Go 应用至关重要。

接下来我们将进一步探讨 channel 的一些更高级的特性和应用场景,包括使用 context 控制 channel 的生命周期、超时处理、以及一些常见的设计模式。

使用 context 控制 channel 的生命周期

在 Go 语言中,context 包提供了一种方式来取消长时间运行的操作或 goroutine。通过将 contextchannel 结合使用,可以更灵活地管理并发任务的生命周期。

示例:

package main

import (
	"context"
	"fmt"
	"time"
)

func worker(ctx context.Context, done chan<- bool) {
	for {
		select {
		case <-ctx.Done(): // 监听 context 的 Done 通道
			fmt.Println("Worker received cancel signal")
			done <- true
			return
		}
		fmt.Println("Working...")
		time.Sleep(1 * time.Second)
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // 确保在退出时取消 context

	done := make(chan bool)
	go worker(ctx, done)

	// 模拟主程序运行一段时间
	time.Sleep(5 * time.Second)

	fmt.Println("Cancelling context")
	cancel() // 取消 context

	<-done // 等待 worker 完成
	fmt.Println("Worker finished.")
}

在这个例子中,worker 函数通过 select 语句监听 contextDone 通道。当 context 被取消时,Done 通道会被关闭,worker 函数会收到信号并停止工作。主函数通过调用 cancel() 方法来取消 context,从而终止 worker 函数。

超时处理

在某些情况下,你可能需要在一定时间内完成某个操作,否则就放弃。Go 语言提供了 select 语句和 time.After 函数来实现超时处理。

示例:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)

	go func() {
		time.Sleep(3 * time.Second)
		ch <- "result"
	}()

	select {
	case res := <-ch:
		fmt.Println("Result received:", res)
	case <-time.After(2 * time.Second):
		fmt.Println("Timeout")
	}
}

在这个例子中,select 语句同时监听 ch 通道和 time.After 返回的通道。如果 ch 在 2 秒内没有接收到结果,time.After 通道会被触发,从而输出 “Timeout”。

常见的设计模式

扇出/扇入(Fan-out/Fan-in)

扇出/扇入是一种常见的并发模式,用于将任务分发给多个 goroutine 并收集结果。

示例:

package main

import (
	"fmt"
	"time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		fmt.Println("Worker", id, "processing job", j)
		time.Sleep(time.Second) // 模拟处理时间
		results <- j * 2
	}
}

func main() {
	const numJobs = 5
	jobs := make(chan int, numJobs)
	results := make(chan int, numJobs)

	// 启动多个 worker
	for w := 1; w <= 3; w++ {
		go worker(w, jobs, results)
	}

	// 分发任务
	for j := 1; j <= numJobs; j++ {
		jobs <- j
	}
	close(jobs)

	// 收集结果
	for a := 1; a <= numJobs; a++ {
		<-results
		fmt.Println("Collected result")
	}
}

在这个例子中,我们创建了多个 worker goroutine 来处理任务。任务通过 jobs 通道分发,结果通过 results 通道收集。main 函数负责分发任务并收集所有结果。

总结

通过上述示例,我们深入了解了 channel 的一些高级用法,包括使用 context 控制 channel 的生命周期、超时处理以及常见的设计模式。这些技术不仅增强了并发编程的能力,还提高了代码的健壮性和可维护性。掌握这些高级特性,可以帮助你在实际项目中更高效地使用 channel

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MyBatisPlus(Spring Boot版)的基本使用

1. 初始化项目 1.1. 配置application.yml spring:# 配置数据源信息datasource:# 配置数据源类型type: com.zaxxer.hikari.HikariDataSource# 配置连接数据库信息driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis_plus?characterEncodi…

【MongoDB】MongoDB的集群,部署架构,OptLog,集群优化等详解

文章目录 一、引入复制集的原因二、复制集成员&#xff08;一&#xff09;基本成员&#xff08;二&#xff09;主节点&#xff08;Primary&#xff09;细化成员 三、复制集常见部署架构&#xff08;一&#xff09;基础三节点&#xff08;二&#xff09;跨数据中心 四、复制集保…

Golang | Leetcode Golang题解之第564题寻找最近的回文数

题目&#xff1a; 题解&#xff1a; func nearestPalindromic(n string) string {m : len(n)candidates : []int{int(math.Pow10(m-1)) - 1, int(math.Pow10(m)) 1}selfPrefix, _ : strconv.Atoi(n[:(m1)/2])for _, x : range []int{selfPrefix - 1, selfPrefix, selfPrefix …

git根据远程分支创建本地新分支

比如我当前本地仓库有4个 remote 仓库&#xff0c;我希望根据其中的一个 <remote>/<branch> 创建本地分支&#xff1a; 先使用 github fetch <remote> 拉取 <remote> 的分支信息&#xff0c;然后在 git checkout -b 创建新分支时使用 -t <remote>…

r-and-r——提高长文本质量保证任务的准确性重新提示和上下文搜索的新方法可减轻大规模语言模型中的迷失在中间现象

概述 随着大规模语言模型的兴起&#xff0c;自然语言处理领域取得了重大发展。这些创新的模型允许用户通过输入简单的 "提示 "文本来执行各种任务。然而&#xff0c;众所周知&#xff0c;在问题解答&#xff08;QA&#xff09;任务中&#xff0c;用户在处理长文本时…

Redis 概 述 和 安 装

安 装 r e d i s: 1. 下 载 r e dis h t t p s : / / d o w n l o a d . r e d i s . i o / r e l e a s e s / 2. 将 redis 安装包拷贝到 /opt/ 目录 3. 解压 tar -zvxf redis-6.2.1.tar.gz 4. 安装gcc yum install gcc 5. 进入目录 cd redis-6.2.1 6. 编译 make …

Android 项目依赖库无法找到的解决方案

目录 错误信息解析 解决方案 1. 检查依赖版本 2. 检查 Maven 仓库配置 3. 强制刷新 Gradle 缓存 4. 检查网络连接 5. 手动下载依赖 总结 相关推荐 最近&#xff0c;我在编译一个 Android 老项目时遇到了一个问题&#xff0c;错误信息显示无法找到 com.gyf.immersionba…

北航软件算法C4--图部分

C4上级图部分 TOPO!步骤代码段TOPO排序部分 完整代码 简单的图图题目描述输入输出样例步骤代码段开辟vector容器作为dist二维数组初始化调用Floyd算法查询 完整代码 负环题目描述输入输出样例步骤代码段全局变量定义spfa1函数用于判断是否有负环spfa2用于记录每个点到1号点的距…

ks 小程序sig3

前言 搞了app版的快手之后 &#xff08;被风控麻了&#xff09; 于是试下vx小程序版的 抓包调试 小程序抓包问题 网上很多教程&#xff0c; github也有开源的工具代码 自行搜索 因为我们需要调试代码&#xff0c;所以就用了下开源的工具 &#xff08;可以用chrome的F12功能&a…

docker:docker: Get https://registry-1.docker.io/v2/: net/http: request canceled

无数次的拉镜像让人崩溃&#xff1a; rootnode11:~/ragflow/docker# more rag.sh #export HTTP_PROXYhttp://192.168.207.127:7890 #export HTTPS_PROXYhttp://192.168.207.127:7890 #export NO_PROXYlocalhost,127.0.0.1,.aliyun.com docker compose -f docker-compose-gpu-C…

基于java Springboot高校失物招领平台

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

菜鸟驿站二维码/一维码 取件识别功能

特别注意需要引入 库文 ZXing 可跳转&#xff1a; 记录【WinForm】C#学习使用ZXing.Net生成条码过程_c# zxing-CSDN博客 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static System.Net.…

使用WebRTC实现点对点实时音视频通信的技术详解

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用WebRTC实现点对点实时音视频通信的技术详解 使用WebRTC实现点对点实时音视频通信的技术详解 使用WebRTC实现点对点实时音视频…

执行flink sql连接clickhouse库

手把手教学&#xff0c;flink connector打通clickhouse大数据库&#xff0c;通过下发flink sql&#xff0c;来使用ck。 组件版本jdk1.8flink1.17.2clickhouse23.12.2.59 1.背景 flink官方不支持clickhouse连接器&#xff0c;工作中难免会用到。 2.方案 利用GitHub大佬提供…

力扣(leetcode)题目总结——辅助栈篇

leetcode 经典题分类 链表数组字符串哈希表二分法双指针滑动窗口递归/回溯动态规划二叉树辅助栈 本系列专栏&#xff1a;点击进入 leetcode题目分类 关注走一波 前言&#xff1a;本系列文章初衷是为了按类别整理出力扣&#xff08;leetcode&#xff09;最经典题目&#xff0c…

基于Java Springboot宠物猫售卖管理系统

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据库&#xff1a;…

Windows docker下载minio出现“Using default tag: latestError response from daemon”

Windows docker下载minio出现 Using default tag: latest Error response from daemon: Get "https://registry-1.docker.io/v2/": context deadline exceeded 此类情况&#xff0c;一般为镜像地址问题。 {"registry-mirrors": ["https://docker.re…

数据结构查找-哈希表(开发地址法+线性探测法)+(创建+查找+删除代码)+(C语言代码)

#include<stdlib.h> #include<stdio.h> #include<stdbool.h> #define NULLKEY -1//单元为空 #define DELKEY -2//单元内容被删除 #define M 20 typedef struct {int key;//关键字int count;//统计哈希冲突探测次数 }HashTable; //插入到哈希表 void InsertHT…

视频直播5G CPE解决方案:ZX7981PG/ZX7981PMWIFI6网络覆盖

方案背景 视频直播蓬勃发展的当下&#xff0c;传统直播网络联网方式的局限性越来越明显。目前传统直播的局限性主要集中在以下几个方面&#xff1a; 传统直播间网络架构条件有限&#xff0c;可连接WIFI数量少&#xff0c;多终端同时直播难以维持&#xff1b;目前4G网络带宽有限…

【电子设计】按键LED控制与FreeRTOS

1. 安装Keilv5 打开野火资料,寻找软件包 解压后得到的信息 百度网盘 请输入提取码 提取码:gfpp 安装526或者533版本都可以 下载需要的 F1、F4、F7、H7 名字的 DFP pack 芯片包 安装完 keil 后直接双击安装 注册操作,解压注册文件夹后根据里面的图示步骤操作 打开说明 STM…