深入理解Go语言中的并发编程【27】【Goroutine的使用、定时器、select】

文章目录

  • Goroutine池
      • worker pool(goroutine池)
  • 定时器
      • 定时器
  • select
      • select多路复用


Goroutine池

worker pool(goroutine池)

  • 本质上是生产者消费者模型
  • 可以有效控制goroutine数量,防止暴涨
  • 需求:
    • 计算一个数字的各个位数之和,例如数字123,结果为1+2+3=6
    • 随机生成数字进行计算
  • 控制台输出结果如下:

null

package mainimport ("fmt""math/rand"
)type Job struct {// idId int// 需要计算的随机数RandNum int
}type Result struct {// 这里必须传对象实例job *Job// 求和sum int
}func main() {// 需要2个管道// 1.job管道jobChan := make(chan *Job, 128)// 2.结果管道resultChan := make(chan *Result, 128)// 3.创建工作池createPool(64, jobChan, resultChan)// 4.开个打印的协程go func(resultChan chan *Result) {// 遍历结果管道打印for result := range resultChan {fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id,result.job.RandNum, result.sum)}}(resultChan)var id int// 循环创建job,输入到管道for {id++// 生成随机数r_num := rand.Int()job := &Job{Id:      id,RandNum: r_num,}jobChan <- job}
}// 创建工作池
// 参数1:开几个协程
func createPool(num int, jobChan chan *Job, resultChan chan *Result) {// 根据开协程个数,去跑运行for i := 0; i < num; i++ {go func(jobChan chan *Job, resultChan chan *Result) {// 执行运算// 遍历job管道所有数据,进行相加for job := range jobChan {// 随机数接过来r_num := job.RandNum// 随机数每一位相加// 定义返回值var sum intfor r_num != 0 {tmp := r_num % 10sum += tmpr_num /= 10}// 想要的结果是Resultr := &Result{job: job,sum: sum,}//运算结果扔到管道resultChan <- r}}(jobChan, resultChan)}
}

定时器

定时器

  • Timer:时间到了,执行只执行1次

  • type Timer

    type Timer struct {C <-chan Time// 内含隐藏或非导出字段
    }
    

    Timer类型代表单次时间事件。当Timer到期时,当时的时间会被发送给C,除非Timer是被AfterFunc函数创建的。


func NewTimer

func NewTimer(d Duration) *Timer

NewTimer创建一个Timer,它会在最少过去时间段d后到期,向其自身的C字段发送当时的时间。


func AfterFunc

func AfterFunc(d Duration, f func()) *Timer

AfterFunc另起一个go程等待时间段d过去,然后调用f。它返回一个Timer,可以通过调用其Stop方法来取消等待和对f的调用。

func (*Timer) Reset

func (t *Timer) Reset(d Duration) bool

Reset使t重新开始计时,(本方法返回后再)等待时间段d过去后到期。如果调用时t还在等待中会返回真;如果t已经到期或者被停止了会返回假。


func (*Timer) Stop

func (t *Timer) Stop() bool

Stop停止Timer的执行。如果停止了t会返回真;如果t已经被停止或者过期了会返回假。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功。

package mainimport ("fmt""time"
)func main() {// 1.timer基本使用//timer1 := time.NewTimer(2 * time.Second)//t1 := time.Now()//fmt.Printf("t1:%v\n", t1)//t2 := <-timer1.C//fmt.Printf("t2:%v\n", t2)// 2.验证timer只能响应1次//timer2 := time.NewTimer(time.Second)//for {// <-timer2.C// fmt.Println("时间到")//}// 3.timer实现延时的功能//(1)//time.Sleep(time.Second)//(2)//timer3 := time.NewTimer(2 * time.Second)//<-timer3.C//fmt.Println("2秒到")//(3)//<-time.After(2*time.Second)//fmt.Println("2秒到")// 4.停止定时器//timer4 := time.NewTimer(2 * time.Second)//go func() {// <-timer4.C// fmt.Println("定时器执行了")//}()//b := timer4.Stop()//if b {// fmt.Println("timer4已经关闭")//}// 5.重置定时器timer5 := time.NewTimer(3 * time.Second)timer5.Reset(1 * time.Second)fmt.Println(time.Now())fmt.Println(<-timer5.C)for {}
}
  • Ticker:时间到了,多次执行

  • type Ticker

    type Ticker struct {C <-chan Time // 周期性传递时间信息的通道// 内含隐藏或非导出字段
    }
    

    Ticker保管一个通道,并每隔一段时间向其传递"tick"。


func NewTicker

func NewTicker(d Duration) *Ticker

NewTicker返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间。它会调整时间间隔或者丢弃tick信息以适应反应慢的接收者。如果d<=0会panic。关闭该Ticker可以释放相关资源。

func (*Ticker) Stop

func (t *Ticker) Stop()

Stop关闭一个Ticker。在关闭后,将不会发送更多的tick信息。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功。


func Sleep

func Sleep(d Duration)

Sleep阻塞当前go程至少d代表的时间段。d<=0时,Sleep会立刻返回。

Example


func After

func After(d Duration) <-chan Time

After会在另一线程经过时间段d后向返回值发送当时的时间。等价于NewTimer(d).C。

Example


func Tick

func Tick(d Duration) <-chan Time

Tick是NewTicker的封装,只提供对Ticker的通道的访问。如果不需要关闭Ticker,本函数就很方便。

Example

package mainimport ("fmt""time"
)func main() {// 1.获取ticker对象ticker := time.NewTicker(1 * time.Second)i := 0// 子协程go func() {for {//<-ticker.Ci++fmt.Println(<-ticker.C)if i == 5 {//停止ticker.Stop()}}}()for {}
}

select

select多路复用

在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。你也许会写出如下代码使用遍历的方式来实现:

for{// 尝试从ch1接收值data, ok := <-ch1// 尝试从ch2接收值data, ok := <-ch2…
}

这种方式虽然可以实现从多个通道接收值的需求,但是运行性能会差很多。为了应对这种场景,Go内置了select关键字,可以同时响应多个通道的操作。

select的使用类似于switch语句,它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。具体格式如下:

    select {case <-chan1:// 如果chan1成功读到数据,则进行该case处理语句case chan2 <- 1:// 如果成功向chan2写入数据,则进行该case处理语句default:// 如果上面都没有成功,则进入default处理流程}
  • select可以同时监听一个或多个channel,直到其中一个channel ready
package mainimport ("fmt""time"
)func test1(ch chan string) {time.Sleep(time.Second * 5)ch <- "test1"
}
func test2(ch chan string) {time.Sleep(time.Second * 2)ch <- "test2"
}func main() {// 2个管道output1 := make(chan string)output2 := make(chan string)// 跑2个子协程,写数据go test1(output1)go test2(output2)// 用select监控select {case s1 := <-output1:fmt.Println("s1=", s1)case s2 := <-output2:fmt.Println("s2=", s2)}
}
  • 如果多个channel同时ready,则随机选择一个执行
package mainimport ("fmt"
)func main() {// 创建2个管道int_chan := make(chan int, 1)string_chan := make(chan string, 1)go func() {//time.Sleep(2 * time.Second)int_chan <- 1}()go func() {string_chan <- "hello"}()select {case value := <-int_chan:fmt.Println("int:", value)case value := <-string_chan:fmt.Println("string:", value)}fmt.Println("main结束")
}
  • 可以用于判断管道是否存满
package mainimport ("fmt""time"
)// 判断管道有没有存满
func main() {// 创建管道output1 := make(chan string, 10)// 子协程写数据go write(output1)// 取数据for s := range output1 {fmt.Println("res:", s)time.Sleep(time.Second)}
}func write(ch chan string) {for {select {// 写数据case ch <- "hello":fmt.Println("write hello")default:fmt.Println("channel full")}time.Sleep(time.Millisecond * 500)}
}

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

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

相关文章

瑞吉外卖-Day01

title: 瑞吉外卖-Day01 abbrlink: ‘0’ date: 2023-04-1 18:00:00 瑞吉外卖-Day01 课程内容 软件开发整体介绍 瑞吉外卖项目介绍 开发环境搭建 后台登录功能开发 后台退出功能开发 1. 软件开发整体介绍 作为一名软件开发工程师,我们需要了解在软件开发过程中的开发流…

从小白到大神之路之学习运维第53天--------tomcat-web应用——————供开发的商城框架

第三阶段基础 时 间&#xff1a;2023年7月5日 参加人&#xff1a;全班人员 内 容&#xff1a; Tomcat应用服务 WEB服务 目录 实验环境&#xff1a;&#xff08;四台服务器&#xff09; 安装tomcat服务&#xff1a; NginxTomcat 负载均衡集群部署&#xff1a; 安装ng…

Python 利用深度学习识别空间推理验证码(一)

注意:本文会比较长,因为空间推理验证码本身比较复杂,我会详细的讲解,我是如何一步一步拆分空间推理的思想去实现的,另外,这里只介绍第一种思想来解决空间推理验证码,实际上,解决该验证码的方法也比较多,这第一种,我会讲解的比较简单,通俗易懂。 注意:下面数据集使用…

HttpRunner自动化之请求中带有 headers 的接口和发送POST请求

headers 可通过headers 添加头部信息&#xff0c;如下图 # 发送请求头headers的接口 - config:name: 百度接口用例base_url: https://www.baidu.com- test:name: 发送百度接口的头部信息request:url: /smethod: GETheaders:Accept: text/html,application/xhtmlxml,applicati…

gitHub

gitHub ** 创建一个仓库 git initgit add .git commit -m “first commit”git branch -M maingit remote add origin gitgithub.com:Dylanmenglliao/demo.gitgit push -u origin main ** 已存在仓库&#xff0c;建立关联 7. git remote add origin gitgithub.com:Dylanme…

遥感云大数据在灾害、水体与湿地领域典型案例及GPT模型

详情点击链接&#xff1a;遥感云大数据在灾害、水体与湿地领域典型案例实践及GPT模型 第一&#xff1a;基础 一&#xff1a;平台及基础开发平台 GEE平台及典型应用案例&#xff1b; GEE开发环境及常用数据资源&#xff1b; ChatGPT、文心一言等GPT模型 JavaScript基础&am…

【Hello mysql】 数据库基础

Mysql专栏&#xff1a;Mysql 本篇博客简介&#xff1a;简单的介绍mysql相关的一些基础知识和在Linux环境下的安装 让大家对于mysql有一个初步的认知 数据库基础 数据库基础数据库定义数据库再理解软件角度文件角度总结 主流数据库mysql安装卸载不要的环境获取mysql官方yum源安装…

java中预览pdf或者图片

GetMapping("preview1")ApiOperation(value "根据文件名实现预览功能")public void previewFile1( HttpServletResponse response) throws IOException {String filePath"C:\\Users\\Harbor Lau\\Desktop\\哈哈\\""11月燃油费(公开).pdf&q…

C# int[,] 和 int[,,]

如标题&#xff1b; 在C#中这两个是定义二维和三维数组的&#xff1b;这和C语言的写法不同&#xff1b; C语言定义二维数组是&#xff0c; int a[5][3]; 看下C#的多维数组&#xff1b;输出数组其中一个值&#xff1b; using System;class Program {static void Main(string[…

Django - 定时任务框架【django-apscheduler】基本使用详解(二)

一. 前言 一个网页会有很多数据是不需要经常变动的&#xff0c;比如说首页&#xff0c;变动频率低而访问量大&#xff0c;我们可以把它静态化&#xff0c;这样就不需要每次有请求都要查询数据库再返回&#xff0c;可以减少服务器压力 我们可以使用Django的模板渲染功能完成页面…

学习Kotlin~类

类 类的field 类定义的每一个属性&#xff0c;kotlin都会产生一个filed,一个setter(),一个getter()field用来存储属性数据&#xff0c;不能直接定义&#xff0c;kotlin会封装&#xff0c;保护它里面数据&#xff0c;只暴露给getter和setter使用只有可变属性才有setter方法需要…

MQTT协议学习

前言 最近在学习mqtt协议&#xff0c;看的是官方英文版的&#xff0c;写这篇博客就是为了将一些关键内容提取出来&#xff0c;以便日后的查询和复习&#xff0c;有需要的可以参考。官方的文档在这&#xff1a; MQTT Essentials - All Core Concepts explained (hivemq.com) …