当前位置: 首页 > article >正文

Go语言交替打印问题及多种实现方法

Go语言交替打印问题及多种实现方法

在并发编程中,多个线程(或 goroutine)交替执行任务是一个经典问题。本文将以 Go 语言为例,介绍如何实现多个 goroutine 交替打印数字的功能,并展示几种不同的实现方法。


Go 语言相关知识点

1. Goroutine

Goroutine 是 Go 语言的轻量级线程,使用 go 关键字启动。它们由 Go 运行时调度,能够高效地并发执行任务。

2. Channel

Channel 是 Go 语言中用于 goroutine 之间通信的管道。通过 channel,goroutine 可以发送和接收数据,实现同步和通信。

  • chan T 表示传输类型为 T 的 channel。
  • 发送数据:ch <- value
  • 接收数据:value := <- ch

3. sync.Mutex

互斥锁,用于保护共享资源,防止多个 goroutine 同时访问导致数据竞争。

4. sync.WaitGroup

用于等待一组 goroutine 完成。通过 Add 设置计数,Done 表示完成,Wait 阻塞直到计数归零。


需求描述

  • n 个 goroutine(线程),编号从 1 到 n。
  • 这 n 个 goroutine 交替打印数字,从 1 打印到 max
  • 例如,3 个 goroutine,打印 1,2,3,4,…30,线程1打印1,线程2打印2,线程3打印3,线程1打印4,依次循环。

方法一:使用多个 Channel 轮流通知(基于题主代码)

思路:

  • 创建 n 个 channel,分别对应每个 goroutine。
  • 每个 goroutine 等待自己的 channel 收到信号后打印数字,然后通知下一个 goroutine。
  • 使用互斥锁保护共享计数器。
  • 使用一个 done channel 通知所有 goroutine 退出。
package mainimport ("fmt""sync"
)func main() {const max = 30const n = 3 // goroutine 数量channels := make([]chan bool, n)for i := 0; i < n; i++ {channels[i] = make(chan bool)}var wg sync.WaitGroupwg.Add(n)counter := 1var mu sync.Mutexdone := make(chan struct{})for i := 0; i < n; i++ {go func(id int) {defer wg.Done()for {select {case <-done:returncase _, ok := <-channels[id]:if !ok {return}mu.Lock()if counter > max {mu.Unlock()close(done)return}fmt.Printf("线程 %d 打印 %d\n", id+1, counter)counter++mu.Unlock()channels[(id+1)%n] <- true}}}(i)}// 启动第一个 goroutinechannels[0] <- truewg.Wait()for i := 0; i < n; i++ {close(channels[i])}fmt.Println("打印结束")
}

方法二:使用单个 Channel 和 goroutine ID 控制

思路:

  • 使用一个 channel 传递当前应该打印的 goroutine ID。
  • 每个 goroutine 监听 channel,只有当收到的 ID 与自己相同时才打印数字。
  • 打印后将下一个 goroutine 的 ID 发送回 channel。
package mainimport ("fmt""sync"
)func main() {const max = 30const n = 3ch := make(chan int)var wg sync.WaitGroupwg.Add(n)counter := 1var mu sync.Mutexfor i := 0; i < n; i++ {go func(id int) {defer wg.Done()for {curID := <-chif curID != id {// 不是自己的轮次,放回去ch <- curIDcontinue}mu.Lock()if counter > max {mu.Unlock()// 结束所有 goroutine// 发送特殊值 -1 表示结束ch <- -1return}fmt.Printf("线程 %d 打印 %d\n", id+1, counter)counter++mu.Unlock()// 发送下一个 goroutine 的 IDch <- (id + 1) % n}}(i)}// 启动第一个 goroutinech <- 0wg.Wait()fmt.Println("打印结束")
}

方法三:使用 sync.Cond 条件变量

思路:

  • 使用一个共享变量 counterturn 表示当前轮到哪个 goroutine 打印。
  • 使用 sync.Cond 来等待和通知 goroutine。
  • 每个 goroutine 等待条件满足(轮到自己),打印数字后更新 turn 并通知其他 goroutine。
package mainimport ("fmt""sync"
)func main() {const max = 30const n = 3var mu sync.Mutexcond := sync.NewCond(&mu)counter := 1turn := 0var wg sync.WaitGroupwg.Add(n)for i := 0; i < n; i++ {go func(id int) {defer wg.Done()for {mu.Lock()for turn != id && counter <= max {cond.Wait()}if counter > max {mu.Unlock()cond.Broadcast()return}fmt.Printf("线程 %d 打印 %d\n", id+1, counter)counter++turn = (turn + 1) % nmu.Unlock()cond.Broadcast()}}(i)}wg.Wait()fmt.Println("打印结束")
}

方法四:使用 Channel + select + 超时退出

思路:

  • 使用一个 channel 传递打印任务。
  • 每个 goroutine 监听 channel,只有当任务分配给自己时打印。
  • 使用超时机制防止死锁。
package mainimport ("fmt""time"
)func main() {const max = 30const n = 3type task struct {id      intcounter int}ch := make(chan task)for i := 0; i < n; i++ {go func(id int) {for {select {case t := <-ch:if t.id != id {// 不是自己的任务,放回去ch <- tcontinue}if t.counter > max {// 结束信号,放回去让其他 goroutine 退出ch <- treturn}fmt.Printf("线程 %d 打印 %d\n", id+1, t.counter)time.Sleep(100 * time.Millisecond) // 模拟工作ch <- task{id: (id + 1) % n, counter: t.counter + 1}case <-time.After(2 * time.Second):// 超时退出return}}}(i)}// 启动第一个任务ch <- task{id: 0, counter: 1}// 等待足够时间让所有打印完成time.Sleep(5 * time.Second)fmt.Println("打印结束")
}

总结

  • Go 语言提供了多种并发原语,能够灵活实现线程间的协作。
  • Channel 是 goroutine 通信的核心,适合用于事件通知和数据传递。
  • sync.Mutex 和 sync.Cond 适合保护共享资源和实现复杂的同步逻辑。
  • 选择哪种方法取决于具体需求和代码风格。

通过以上几种方法,你可以根据实际场景选择合适的实现方式,实现多个 goroutine 交替打印数字的功能。

相关文章:

Go语言交替打印问题及多种实现方法

Go语言交替打印问题及多种实现方法 在并发编程中&#xff0c;多个线程&#xff08;或 goroutine&#xff09;交替执行任务是一个经典问题。本文将以 Go 语言为例&#xff0c;介绍如何实现多个 goroutine 交替打印数字的功能&#xff0c;并展示几种不同的实现方法。 Go 语言相关…...

ArcGIS Pro调用多期历史影像

一、访问World Imagery Wayback&#xff0c;基本在我国范围 如下图&#xff1a; 二、 放大到您感兴趣的区域 三、 查看影像版本信息 点击第二步的按钮后&#xff0c;便可跳转至World Imagery (Wayback 2025-04-24)的相关信息。 四 、点击上图影像版本信息&#xff0c;页面跳转…...

10.11 LangGraph多角色Agent开发实战:生产级AI系统架构与性能优化全解析

LangGraph 项目:High-level API for Multi-actor Agents 关键词:LangGraph 多角色 Agent, 状态管理, 持久化机制, 工作流编排, 生产级 AI 系统 1. LangGraph 设计哲学与架构演进 LangGraph 是 LangChain 生态中首个面向 多角色协作 Agent 的高阶 API 框架,其核心设计思想可…...

组态王|组态王中如何添加西门子1200设备

哈喽,你好啊,我是雷工! 最近使用组态王采集设备数据,设备的控制器为西门子的1214CPU, 这里边实施边记录,以下为在组态王中添加西门子1200PLC的笔记。 1、新建 在组态王工程浏览器中选择【设备】→点击【新建】。 2、选择设备 和设备建立通讯要通过对应的设备驱动。 在…...

发布时将多个bpl 打包成一个bpl的方法,或者说:不需要vcl60.bpl情况下 18.5K的exe 照常可以运行。

其实这种方式 就是把项目的逻辑和业务 和 依赖分开。 控件和IDE 相对来说一段时间内不会改变。 更新只是更新一些项目的逻辑&#xff0c;例如你在代码里多写了一个 if &#xff0c;这样就可以只更新这个极小的exe。 题&#xff1a;关于bpl发布时将vcl60.bpl&#xff0c;vcld…...

6.2.2邻接表法-图的存储

知识总览&#xff1a; 为什么要用邻接表 因为邻接矩阵的空间复杂度高(O(n))&#xff0c;且不适合边少的稀疏图&#xff0c;所以有了邻接表 用代码表示顶点、图 声明顶点图信息 声明顶点用一维数组存储各个顶点的信息&#xff0c;一维数组字段包括2个&#xff0c;每个顶点的…...

C++23 放宽范围适配器以允许仅移动类型(P2494R2)

文章目录 引言背景与动机提案内容与实现细节提案 P2494R2实现细节编译器支持 对开发者的影响提高灵活性简化代码向后兼容性 示例代码总结 引言 C23 标准中引入了许多重要的改进&#xff0c;其中一项值得关注的特性是放宽范围适配器&#xff08;range adaptors&#xff09;以允…...

【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer

【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer spring-kafka官方文档: https://docs.spring.io/spring-kafka/docs/2.8.10/reference/pdf/spring-kafka-reference.pdf KafkaTemplate API: https://docs.spring.io/spring-kafka/api/org/springframe…...

Spring Boot三层架构设计模式

Spring Boot 的三层架构设计模式是一种经典的软件分层设计模式&#xff0c;旨在将应用程序划分为 表现层&#xff08;Controller&#xff09;、业务逻辑层&#xff08;Service&#xff09;、数据访问层&#xff08;Repository/DAO&#xff09;&#xff0c;通过清晰的职责划分提…...

在Java中调用Ant命令

在Java中调用Ant命令 在Java程序中调用Ant命令有几种方法&#xff0c;下面介绍两种常用的方式&#xff1a; 1. 使用Runtime.exec()方法 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class AntRunner {public stat…...

WebRTC技术下的EasyRTC音视频实时通话SDK,助力车载通信打造安全高效的智能出行体验

一、方案背景​ 随着智能交通与车联网技术的飞速发展&#xff0c;车载通信在提升行车安全、优化驾驶体验以及实现智能交通管理等方面发挥着越来越重要的作用。传统的车载通信方式在实时性、稳定性以及多媒体交互能力上存在一定局限&#xff0c;难以满足现代车载场景日益复杂的…...

数据科学和机器学习的“看家兵器”——pandas模块 之二

目录 pandas 模块介绍 4.2 pandas 数据读取 4.2.1 课程目标 4.2.2 读取 Excel 文件中的数据 (一)读取某个工作表中的数据 (二)读取指定数据列的标签内容 (三)读取指定数据行的标签内容 (四)读取指定行或者列 4.2.3、读取 CSV 文件数据 4.2.4、课程总结回顾 4.2.5、课后…...

本地部署Firecrawl+Dify调用踩坑记录

最近自己研究Dify&#xff0c;使用到Firecrawl这个比较好用的工具。用Firecrawl官网的不知道为什么总是卡住得不到结果&#xff0c;于是我打算自己去本地部署一个。好家伙真给我人搞麻了&#xff0c;太多问题了。 我是在京东云上面租的一台服务器。 首先就是docker的安装&…...

MySQL--day2--基本的select语句

&#xff08;以下内容全部来自上述课程&#xff09; SQL概述 结构化查询语句 1. SQL分类 DDL&#xff1a;数据定义&#xff08;definition&#xff09;语言&#xff1a;create、drop、alter… DML&#xff1a;数据操作&#xff08;manipulation&#xff09;语言&#xff…...

什么是dom?作用是什么

DOM 的定义 DOM&#xff08;Document Object Model&#xff0c;文档对象模型&#xff09;是 HTML 和 XML 文档的编程接口。它将文档解析为一个由节点和对象组成的树状结构&#xff0c;允许开发者通过编程方式动态访问和操作文档的内容、结构和样式。 DOM 的作用 DOM 的主要作…...

Trae - 国人Cursor的免费平替产品

前情提要&#xff1a;Cursor-零基础使用flutter完成贪吃蛇游戏-迄今为止世上最牛的AI编辑工具&#xff0c;不需要程序员也可以编程 Cursor 不是我的最佳选择 Cursor 是基于 VSCode 进化而来&#xff0c;虽然好用&#xff0c;但总结下来有几点点是我有所顾虑的。 第一&#x…...

自动化:批量文件重命名

自动化&#xff1a;批量文件重命名 1、前言 2、效果图 3、源码 一、前言 今天来分享一款好玩的自动化脚&#xff1a;批量文件重命名 有时候呢&#xff0c;你的文件被下载下来文件名都是乱七八糟毫无规律&#xff0c;但是当时你下载的时候没办法重名或者你又不想另存为重新重…...

Jsoup库和Apache HttpClient库有什么区别?

Jsoup 和 Apache HttpClient 是两个功能不同的库&#xff0c;它们在 Java 开发中被广泛使用&#xff0c;但用途和功能有明显的区别&#xff1a; Jsoup 用途&#xff1a;Jsoup 是一个用于解析 HTML 文档的库。它提供了非常方便的方法来抓取和解析网页内容&#xff0c;提取和操作…...

学习!FastAPI

目录 FastAPI简介快速开始安装FastApiFastAPI CLI自动化文档 Reqeust路径参数Enum 类用于路径参数路径参数和数值校验 查询参数查询参数和字符串校验 请求体多个请求体参数嵌入单个请求体参数 CookieHeader表单文件直接使用请求 ResponseResponse Model多个关联模型 响应状态码…...

Linux 安装 Unreal Engine

需要对在unreal engine官网进行绑定github账号&#xff0c;然后到unreal engine github仓库中进行下载对应的版本&#xff0c;并进行安装unreal engine官网 github地址...

【第三十六周】LoRA 微调方法

LoRA 摘要Abstract文章信息引言方法LoRA的原理LoRA在Transformer中的应用补充其他细节 实验与分析LoRA的使用论文实验结果分析 总结 摘要 本篇博客介绍了LoRA&#xff08;Low-Rank Adaptation&#xff09;&#xff0c;这是一种面向大规模预训练语言模型的参数高效微调方法&…...

什么是 Boosting

什么是 Boosting Boosting 通过按顺序纠正错误并将弱学习器组合成强预测器来提高机器学习性能。机器学习的最新进展引入了解决复杂问题的新方法。Boosting 是一种不断显示出希望的技术。它通过使用多种算法来提高性能&#xff0c;从而改变了我们进行数据建模的方式。随着 Boost…...

Redis 数据类型与操作完全指南

Redis 是一个开源的、内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。与传统的关系型数据库不同&#xff0c;Redis 提供了丰富的数据类型和灵活的操作方式&#xff0c;这使得它能够高效地解决各种不同场景下的数据存储和处理问题。本文将全面介绍 R…...

Digi XBee XR 系列介绍

Digi 延续了 20 多年来亚 GHz 射频模块的传统&#xff0c;推出了 Digi XBee XR 系列远距离模块&#xff0c;包括 Digi XBee XR 900 - 已通过多个地区的预先认证 - 以及 Digi XBee XR 868 - 已通过欧洲地区应用的预先认证。 这些先进的射频模块专为远距离抗干扰无线通信而设计。…...

【方法论】金字塔原理概述:写作逻辑的底层架构与实践法则

文章目录 一、为何采用金字塔结构&#xff1a;对抗认知局限的思维框架1、 梳理逻辑&#xff0c;抽象归纳2、自上而下&#xff0c;结论居首3、 结论先行之必要 三、金字塔结构1、纵向逻辑&#xff1a;上层思想必须是下层思想的概括提炼2、横向逻辑&#xff1a;每组思想需属于同一…...

深入探索 OpenCV:从实时视频流到图像处理的实战指南

引言 在当今数字化时代&#xff0c;计算机视觉技术正逐渐成为推动科技发展的核心力量之一。从自动驾驶汽车到智能家居设备&#xff0c;从医疗影像诊断到工业自动化&#xff0c;计算机视觉的应用无处不在。而 OpenCV&#xff08;Open Source Computer Vision Library&#xff0…...

BERT 核心技术全解析:Transformer 双向编码与掩码语言建模的底层逻辑

一、引言&#xff1a;从 BERT 到生成式 AI 的进化之路 科学的突破从来不是孤立的奇迹&#xff0c;而是人类知识长河中无数基石的累积。 当我们惊叹于 ChatGPT、Google Bard 等大型语言模型&#xff08;LLM&#xff09;在生成式 AI 领域的惊人表现时&#xff0c;不能不回溯到 20…...

【OpenCV基础 1】几何变换、形态学处理、阈值分割、区域提取和脱敏处理

目录 一、图像几何变化 1、对图片进行放大、缩小、水平放大和垂直放大 2、旋转、缩放、控制画布大小 二、图像形态学处理 1、梯度运算 2、闭运算 3、礼帽运算 4、黑帽运算 三、图像阈值分割 1、二值化处理 2、反二值化处理 3、截断阈值处理 4、超阈值零处理 5、低…...

CSS- 4.4 固定定位(fixed) 咖啡售卖官网实例

本系列可作为前端学习系列的笔记&#xff0c;代码的运行环境是在HBuilder中&#xff0c;小编会将代码复制下来&#xff0c;大家复制下来就可以练习了&#xff0c;方便大家学习。 HTML系列文章 已经收录在前端专栏&#xff0c;有需要的宝宝们可以点击前端专栏查看&#xff01; 点…...

得力标签打印机系统集成方案的技术应用与场景实践

一、方案背景与技术特性 在物联网设备管理场景中&#xff0c;标签打印的自动化与效率提升成为企业数字化升级的重要需求。得力标签打印机驱动及系统集成方案&#xff0c;通过技术接口开发与硬件协同&#xff0c;为设备标识管理提供 轻量化对接能力。以下从技术适配性与功能设计…...