Go实现两个goroutine交替打印切片并实现超时控制

go语言实现两个goroutine交替执行打印数字和字母,效果:12AB34CD56EF... go语言实现两个goroutine交替执行打印数字和字母,效果如下: 12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ… 这是一道某公司golang开发工程师的面试题, 考验了对goroutine和channl的使用, 更验证了go语言的CSP并发模型—要通过通信来共享内存. CSP并发模型 CSP并发模型是... 阅读详情

目录

题目描述

解决方案

实现

完整代码

传送门


题目描述

请用代码实现一个函数:该函数接收一个整型类型的切片作为参数,函数体使用两个goroutines交替打印切片中的元素。该函数还需要一个参数实现超时控制,在指定的时间后,若切片还没有打印完,要求停止两个goroutines和该函数的执行。

解决方案

对于超时问题,我们可以使用context包中的WithTimeout函数来约束:

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

for {
    switch {
    case <- ctx.Done():
        // timeout...
    default:
       // ...
    }
}

对于交替打印,为了避免两个goroutine打印同一个值,可以让一个goroutine打印切片索引为奇数的值,另一个goroutine打印切片索引为偶数的值。仅仅区分开两个goroutine打印的值还不够,因为不同的goroutine打印的速度不一定相同,因此不能严格的实现“交替打印”。因此需要用一个channel来控制goroutines,让一个goroutine打印完后等待另一个goroutine打印,然后再执行下一轮打印步骤。

实现

题目的目的是实现一个函数,其有两个参数,一个是要打印的切片,另一个是超时时间。为了测试覆盖到更多的场景(比如大长度切片),我们总不能手写一个固定长度的切片去验证我们后续的逻辑,因此,需要写一个生成指定长度的切片的函数:

func GenerateList(min, max int) []int {
	result := make([]int, 0)
	for i := min; i < max; i++ {
		result = append(result, i)
	}
	return result
}

函数GenerateList生成一个数值连续的切片,它有两个参数,它表示生成的切片的数值范围。min表示切片的最小值,max表示切片的最大值 - 1。

下一步,根据题目的需要,我们需要定义一个函数,用来交替打印切片并实现超时控制,因此该函数要接收两个参数,一个待打印的切片,一个超时时间:

func PrintArray(list []int, timeout float64) {}

我们继续往下走。接下来考虑超时问题:

func PrintArray(list []int, timeout float64) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
	defer cancel()

	for {
		select {
		case <- ctx.Done():
			fmt.Println("timeout...")
			return
		}
	}
}

上面的代码中,我们设置了超时时间,并在函数末尾使用一个无限循环来检查是否超时。一旦超时,从cex.Done管道取出消息将不会被阻塞,因此执行return,函数结束执行。设置好了超时退出后,我们接下来继续创建两个goutinue,用来打印切片。我们让第一个goroutine打印切片的奇数索引对应的值,让第二个goroutine打印偶数索引对应的值:

func PrintArray(list []int, timeout float64) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
	defer cancel()


	go func() {
		for i := 0; i < len(list); i++ {
			if i % 2 == 0 {
				fmt.Println("goroutine-1: ", list[i])
			}
		}

	} ()

	go func() {
		for i := 0; i < len(list); i++ {
			if i % 2 == 1 {
				fmt.Println("goroutine-2: ", list[i])
			}
		}

	} ()

	for {
		select {
		case <- ctx.Done():
			fmt.Println("timeout...")
			return
		}
	}
}

看起来不错。但是这样的代码依然达不到要求:两个goroutine依然不是交替打印,有的时候第一个goroutine会比第二个goroutine打印的快,而有的时候第二个比第一个快。两个goroutine各跑各的,不受控制,这显然不是我们期望的样子。为了让两个goroutine配合协调,其中一个打印完要等等另一个,因此我们需要添加一个channel来控制这两个goroutine:

func PrintArray(list []int, timeout float64) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
	defer cancel()

	allow := make(chan bool)

	go func() {
		for i := 0; i < len(list); i++ {
			allow <- true
			if i % 2 == 0 {
				fmt.Println("goroutine-1: ", list[i])
			}
		}

	} ()

	go func() {
		for i := 0; i < len(list); i++ {
			<- allow
			if i % 2 == 1 {
				fmt.Println("goroutine-2: ", list[i])
			}
		}

	} ()

	for {
		select {
		case <- ctx.Done():
			fmt.Println("timeout...")
			return
		}
	}
}

allow管道控制了两个goroutine循环的同步:当两个goroutine进入循环后,始终保证两个goroutine循环的i值相同。这样通过奇数和偶数的判断,最终每一次循环中只有一个goroutine可以打印。其实到这里,我们的代码已经完全实现了题目要求的所有内容,交替打印,超时退出。但是有一个十分不友好的bug:如果切片的长度很短,而超时时间设置的很长,那么select会一直等待超时结束,我们的程序虽然早早的交替打印完毕也不能退出,只能等待超时触发。为了让程序再打印结束后提前退出,我们需要引入一个打印完毕的判断:

func PrintArray(list []int, timeout float64) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
	defer cancel()

	allow := make(chan bool)
	finish := make(chan string)
	finishedCount := 0

	go func() {
		for i := 0; i < len(list); i++ {
			allow <- true
			if i % 2 == 0 {
				fmt.Println("goroutine-1: ", list[i])
			}
		}

		finish <- "goroutine-1"
	} ()

	go func() {
		for i := 0; i < len(list); i++ {
			<- allow
			if i % 2 == 1 {
				fmt.Println("goroutine-2: ", list[i])
			}
		}

		finish <- "goroutine-2"
	} ()

	for {
		select {
		case <- ctx.Done():
			fmt.Println("timeout...")
			return
		case <- finish:
			finishedCount += 1
			if finishedCount == 2 {
				return
			}
		}
	}
}

这次改动新增了finish管道和finishCount变量。再每一个goroutine正常打印完毕后,都向finish管道写入数据,而函数末尾的select语句中,会接收finish管道,一旦有数据取出,就给初始值为0的finishedCount自增1。当finishedCount值为2时,程序退出,不需要再等到超时。

完整代码

package main

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

func PrintArray(list []int, timeout float64) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
	defer cancel()

	allow := make(chan bool)
	finish := make(chan string)
	finishedCount := 0

	go func() {
		for i := 0; i < len(list); i++ {
			allow <- true
			if i % 2 == 0 {
				fmt.Println("goroutine-1: ", list[i])
			}
		}

		finish <- "goroutine-1"
	} ()

	go func() {
		for i := 0; i < len(list); i++ {
			<- allow
			if i % 2 == 1 {
				fmt.Println("goroutine-2: ", list[i])
			}
		}

		finish <- "goroutine-2"
	} ()

	for {
		select {
		case <- ctx.Done():
			fmt.Println("timeout...")
			return
		case <- finish:
			finishedCount += 1
			if finishedCount == 2 {
				return
			}
		}
	}
}

func GenerateList(min, max int) []int {
	result := make([]int, 0)
	for i := min; i < max; i++ {
		result = append(result, i)
	}
	return result
}

func main() {
	demo := GenerateList(0, 5)
	PrintArray(demo, 1.1)
}

修改main函数中的GenerateList参数值可以调整切片的长度,调整PrintArray函数的timeout参数可以调整超时时间。

传送门

go make函数:创建map, slice和channel

go append函数:给切片添加元素/合并切片

go fmt.Println函数:将数据打印到控制台

context.WithTimeout:实现超时控制icon-default.png?t=LBL2https://blog.youkuaiyun.com/TCatTime/article/details/121722588

FFmpeg音频转换避坑实录:为什么VOS的REC转MP3比WAV转MP3体积小一半? 本文深入解析了FFmpeg在VOS系统REC格式音频转换中的优化技巧,揭示了REC转MP3比WAV转MP3体积小一半的原因。通过分析REC格式的压缩特性、转码参数陷阱及FFmpeg调优方法,帮助开发者实现高效音频转换,显著降低存储成本。 阅读详情

相关推荐

IGBT模块封装中的银烧结技术:为什么特斯拉和比亚迪都在用这种黑科技?

本文深入解析了IGBT模块封装中的银烧结技术,揭示了其作为功率半导体封装工艺关键升级的原因。该技术通过低温烧结形成高熔点、高导热导电的银连接层,显著提升了模块的功率循环寿命、散热能力和高温工作稳定性,这正是特斯拉、比亚迪等行业巨头采用此项黑科技以释放电动汽车电驱系统极致性能的核心所在。

yhn4567890的博客 833

Goroutine交替执行的相关问题与方法

多个Goroutine交替执行输出 文章目录多个Goroutine交替执行输出两个协程交替打印1-100的奇偶数使用channel作为信号传递实现使用runtime让协程竞争CPU使用sync的锁竞争实现N个线程交替打印0-100没有严格的交替顺序的时候有严格交替顺序的时候channel实现线程池参考资料 Go语言的协程goroutine在高并发方面具有优势,最简单的可以写一个简单的生产者消费者模型。 func mode1() { rand.Seed(time.Now().UnixNano()) wg

m0_60647847的博客 1906

华为海思IC前端中后端(COT&XPU)岗位笔试机考题

华为海思COT和XPU部门IC前端,IC中端,IC后端岗位笔试真题分享

weixin_37584728的博客 3362

Golang面试题目-使用两个goroutine交替打印数字和字母

题目:使⽤两个goroutine交替打印序列,⼀个goroutine打印数字, 另外⼀个goroutine打印字母,实现效果如下: 解答:题目要求用两个goroutine来交替打印数字和字母,核心问题是交替这两个字,涉及到两个进程之间的通信问题了,需用channel来控制打印的进度。 使用两个channel来分别控制打印数字的goroutine(A)和打印字母的goroutine(B)序列,A完成后通过channel通知B进行字母打印,B完成后通知A进行数字打印,然后周而复始运行。

luyaran的博客 1769

PWM(脉宽调制)技术详解:从基础到应用实践示例

PWM(脉宽调制)技术是嵌入式系统中一项重要的控制技术,通过调节脉冲宽度来实现对模拟设备的精准控制。从LED亮度到电机速度,PWM的应用场景非常广泛。对于嵌入式学习者来说,理解PWM的工作原理、掌握其配置方法,不仅能提升编程能力,还能为更多复杂系统的开发打下坚实的基础。

SpringWJN的博客 6325

机场数据治理系列介绍(1):机场行业数据中台中的数据管理顶层设计方案

民航机场行业的数据中台中,有关数据管理顶层设计方案需要结合机场行业的业务特点和技术需求进行具体的设计,今日我们简要考量设计提供一个参考方案:

机场信息系统研究员的专栏 1604

golang使用channel通道实现非阻塞队列和超时阻塞队列

Channel是Golang中非常重要的数据结构, golang的channel读或写是会造成阻塞的,但是可以用select的多路复用解决这个问题。

西京刀客 2793

基于IMX429-IMX430-IMX432-IMX437系列传感器的SLVS-MIPI桥接方案设计与实现

本文详细介绍了针对索尼IMX429、IMX430、IMX432及IMX437系列传感器的SLVS-MIPI桥接方案设计与实现。针对工业视觉、车载摄像头等应用中主流SoC仅支持MIPI接口的痛点,提出基于FPGA(如Lattice Crosslink-NX)的透传桥接方案,将传感器输出的SLVS信号无损转换为MIPI CSI-2信号,确保数据完整性与低延迟,并探讨了硬件选型、逻辑设计及多传感器兼容性等关键问题。

weixin_29230901的博客 461

golang:channel的超时

基本语法 通道声明和初始化 我们可以通过chan关键字来声明通道类型变量 var ch chan int // 声明一个通道类型变量 ch,并且通道中只能传递 int 类型数据 我们还可以通过如下方式声明通道数组、切片、字典,以下声明方式表示 chs 中的元素都是 chan int 类型的通道: var chs [10]chan int var chs []chan int var chs map[string]chan int 不过,实际编码时,我们更多使用的是下面这种快捷方式同时声明和初始化通道类

OceanStar的博客 2274

42、InfiniBand技术详解:QP状态机、操作及错误处理

本文深入解析InfiniBand技术中的核心组件QP(Queue Pair)状态机、工作请求处理机制、支持的操作类型及错误处理策略。详细介绍了QP的创建与状态转换流程,包括INIT、RTR、RTS等关键状态,并提供了状态修改的代码示例。文章还探讨了工作请求的发布、资源使用限制、围栏机制以及重试和RNR流程,帮助开发者理解通信可靠性保障机制。此外,涵盖了多播组的应用场景与API使用方法,并对比了用户空间与内核级RDMA API的调用差异,最后给出了实践建议和常见问题解答,为构建高性能分布式系统提供技术指导。

oauth7security的博客 131

使用Captum库对CIFAR数据集进行模型可解释性分析

为了训练模型,我们需要指定损失函数和优化技术。损失函数用于衡量模型的性能,在训练过程中,我们通过调整模型参数来最小化损失。本教程使用。

2501_90323865的博客 178

Golang面试题: 3个goroutine交替打印ABC

文章目录题目介绍做法一题目扩展 开心一刻        一位小帅哥和靓姑娘在火车上相遇。经过一阵“天南地北,生猛海鲜”之后,小帅哥拿出一副扑克与姑娘对玩:   帅哥:QQK?(谈谈看)   姑娘:Q45?(谈什么)   帅哥:Q21!(谈恋爱)   姑娘:8Q!!(不谈)   $%……!   快到站了,小帅哥不甘心,又拿出扑克:   帅哥:3QQK?(再谈谈看)   姑娘:948Q!!!(就是不谈)   帅哥:—%¥……) 题目介绍     &nb

xingyu97的博客 4224

Allegro如何导出和导入器件模型Signal_Model操作指导

在用Allegro做PCB设计的时候,通常需要给器件加上Signal_Model,在做等长的时候用到的非常频繁。如下图,需要把R7002的Signal_Model导入到另外一块没有给R7002加好模型的BRD中。Allegro除了可以给器件添加模型,还支持从一块加好模型的BRD导入到另外一块BRD中,如何导出和导入器件模型Signal_Model操作指导。

weixin_54787054的博客 1896

golang编程题2

1、给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。比如,切片 [23,32,78,43,76,65,345,762,......915,86],查找目标值为 345 ,如果切片中存在,则目标值输出"Found it!2、请实现一个算法,在不使用【额外数据结构和储存空间】的情况下,翻转一个给定的字符串(可以使用单个过程变量)。给定一个string,请返回一个string,为翻转后的字符串。5、假设有一个超长的切片,切片的元素类型为int,切片中的元素为乱序排序。

qq_28090573的博客 327

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

go语言浅析

1)变量:大写字母开头其他包可直接用,小写字母开头需要传值才可调用定义方式: 变量名 := 具体值/var 变量名(类型)(=值)/var(...)2)常量:常量名全大写定义方式:const 常量名 (类型)= 值/const(...)ps:如果枚举时常量未赋初值,则与之前最近类型一致常量值相等接口代表一种调用契约,是多个方法声明的集合。在动态语言中,接口(interface)也被称为协议(protocol)。准备交互的双方,共同遵守事先约定的规则,使得在无须知道对方身份的情况下进行写作。

zydbk的博客 538

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Go语言实现超时的3种方法

超时,指一个协程A开启另一个协程B,A会阻塞等待B一段指定的时间,例如:5秒,A通知B结束。也就是说,A就不愿意阻塞等待太久。 Go语言有多种方法实现这种超时,我总结出3种: 方法一:用两个通道 + A协程sleep 一个通道用来传数据,一个用来传停止信号。 package main import ( "fmt" "time" ) // 老师视频里的生产者消费者 func main() { //知识点: 老师这里用了两个线程,一个用个传数据,一个用来传关闭信号 messages :=

docker、kubernetes学习笔记 4591

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Go交换切片内两个索引对应的值

交换Go 切片内两个索引对应的值 方法演示。

TCatTime的博客 1876

Golang协程-通道超时处理

Golang协程-通道超时处理

深漂小码哥 391

17. Goroutine 的并发与超时控制

Goroutine 的并发与超时控制 文章目录并发按序返回超时控制并发限制 并发 package main import ( "fmt" "time" ) func run(task_id, sleeptime int, ch chan string) { time.Sleep(time.Duration(sleeptime) * time.Second) ch...

kaifei的博客 1257

使用select完成goroutine中超时功能

使用select完成goroutine中超时功能虽然携程是go语言中一个新的概念,嗯,但它本质上依然是属于多线程。超时机制是多线程中是一个非常重要的保障程序的鲁棒性的一个措施;错误是很难预估的,在多线程中更为显著,更容易出现难以预料的错误。一个异常场景在go语言中使用channel时需要小心,比如这个用法i := <-ch不出问题的话这个是一个很简单的,从ch中读取数据到i中。但如果ch一直没有人往

绯浅yousa的笔记 2554

go context 子Goroutine超时控制

context使用 Go语言第一形参通常都为context.Context类型,1. 传递上下文 2. 控制子Goroutine超时退出 3. 控制子Goroutine定时退出 package main import ( "context" "fmt" "time" ) func main() { ctx, cancel := context.WithTimeout(context....

牛奔的博客 720

超时的goroutine如何回收?

谈到goroutine并发超时控制,一搜很容易看到下面这样的代码。 func TestGoroutine3(t *testing.T) { done := make(chan struct{}, 0) go func() { // do something time.Sleep(time.Second) fmt.Println("server") done <- struct{}{} fmt.Println("send done success!") }() selec

outsider 1524

golang基础-chan的select操作、定时器操作、超时控制、goroutine中使用recover

chan的只读和只写 a.只读chan的声明 Var 变量的名字 Var readChan b. 只写chan的声明 Var 变量的名字 chan Var writeChan chan chan的select操作 格式如下 Select { case u := <- ch1: case e := <- ch2: default: } 看实例

全干工程师 6791

Go语言之goroutine的调度原理

转自本同步公众号:“灰子学技术” 一、关于并发的基础知识 再讲goroutine的调度原理之前,有些与操作系统相关的知识,我们需要先知道,例如: 1.什么是并发? 并发:两个或两个以上的任务在一段时间内被执行。我们并不关心这些任务是否在同一时刻执行,我们只是知道,这些任务在这一段时间能能够都被执行,当然这一段时间可以很长,也可以很短。 2.并发的最小并发单位是什么? 进程是计算...

zhghost的专栏 723

Golang面试题: 两个goroutine交替打印1-100之间的奇数和偶数

文章目录题目介绍做法一做法二做法三 开心一刻        一个国王要替公主征婚,把一个苹果放在公主头上,谁要把它射中就有机会迎娶公主。        第一个男士把苹果射中,他说:“I’m 罗宾。”        第二个男士也把苹果射中,他说:“I’m 后羿。”        第三个男士不小心把公主射死了,他说:“ I’m sorry…" 题目介绍

xingyu97的博客 4512

go实现N个goroutine交替打印数字

go中CSP模型提倡“不要以共享内存的方式来通信,相反,要以通信来共享内存”,go的CSP模型,就是通过goroutine和channel结合的形式来实现的。 这里通过goroutine和channel实现了一个简单的并发控制,即通过channel在不同的goroutine之间传递信息,而不是通过锁的形式,或者是共享变量的形式来实现。 例如要实现3个goroutine交替输出1-30的数字,可以参考下面的实现方法: 下方会有解释 func getWorker(waitCh chan int, s

liyunlonghere 2531
上一篇: Go 变量
下一篇: Python list count方法:记录特定值在列表中的出现次数
TCatTime
博客等级 码龄9年 400粉丝 256原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值