《快学 Go 语言》第 11 课 —— 千军万马跑协程

Selenium+Python自动化测试实战项目 维度unittestpytest学习成本低(标准库)中等语法简洁性❌ 需继承类✅ 函数式风格参数化支持需ddt扩展✅ 原生支持并发执行❌ 不支持✅xdist支持报告能力文本输出HTML/Allure 多种选择社区活跃度稳定但缓慢极其活跃✅结论:新项目强烈建议使用 pytest。不过好消息是,pytest可以直接运行unittest写的测试,迁移毫无压力。 阅读详情

640?wx_fmt=jpeg

协程和通道是 Go 语言作为并发编程语言最为重要的特色之一,初学者可以完全将协程理解为线程,但是用起来比线程更加简单,占用的资源也更少。通常在一个进程里启动上万个线程就已经不堪重负,但是 Go 语言允许你启动百万协程也可以轻松应付。如果把协程比喻成小岛,那通道就是岛屿之间的交流桥梁,数据搭乘通道从一个协程流转到另一个协程。通道是并发安全的数据结构,它类似于内存消息队列,允许很多的协程并发对通道进行读写。

640?wx_fmt=png
图片

Go 语言里面的协程称之为 goroutine,通道称之为 channel。

协程的启动

Go 语言里创建一个协程非常简单,使用 go 关键词加上一个函数调用就可以了。Go 语言会启动一个新的协程,函数调用将成为这个协程的入口。

package main

import "fmt"
import "time"

func main() {
    fmt.Println("run in main goroutine")
    go func() {
        fmt.Println("run in child goroutine")
        go func() {
            fmt.Println("run in grand child goroutine")
            go func() {
                fmt.Println("run in grand grand child goroutine")
            }()
        }()
    }()
    time.Sleep(time.Second)
    fmt.Println("main goroutine will quit")
}

-------
run in main goroutine
run in child goroutine
run in grand child goroutine
run in grand grand child goroutine
main goroutine will quit

值得注意的是这里的 go 关键字语法和前面的 defer 关键字语法是一样的,它后面跟了一个匿名函数,然后还要带上一对(),表示对匿名函数的调用。

上面的代码中主协程睡眠了 1s,等待子协程们执行完毕。如果将睡眠的这行代码去掉,将会看不到子协程运行的痕迹

-------------
run in main goroutine
main goroutine will quit

子协程异常退出

在使用子协程时一定要特别注意保护好每个子协程,确保它们正常安全的运行。因为子协程的异常退出会将异常传播到主协程,直接会导致主协程也跟着挂掉,然后整个程序就崩溃了。

package main

import "fmt"
import "time"

func main() {
    fmt.Println("run in main goroutine")
    go func() {
        fmt.Println("run in child goroutine")
        go func() {
            fmt.Println("run in grand child goroutine")
            go func() {
                fmt.Println("run in grand grand child goroutine")
                panic("wtf")
            }()
        }()
    }()
    time.Sleep(time.Second)
    fmt.Println("main goroutine will quit")
}

---------
run in main goroutine
run in child goroutine
run in grand child goroutine
run in grand grand child goroutine
panic: wtf

goroutine 34 [running]:
main.main.func1.1.1()
    /Users/qianwp/go/src/github.com/pyloque/practice/main.go:14 +0x79
created by main.main.func1.1
    /Users/qianwp/go/src/github.com/pyloque/practice/main.go:12 +0x75
exit status 2

为了保护子协程的安全,通常我们会在协程的入口函数开头增加 recover() 语句来恢复协程内部发生的异常,阻断它传播到主协程导致程序崩溃。

go func() {
  if err := recover(); err != nil {
    // log error
  }
  // do something
}()

启动百万协程

Go 语言能同时管理上百万的协程,这不是吹牛,下面我们就来编写代码跑一跑这百万协程,读者们请想象一下这百万大军同时奔跑的感觉。

package main

import "fmt"
import "time"

func main() {
    fmt.Println("run in main goroutine")
    i := 1
    for {
        go func() {
            for {
                time.Sleep(time.Second)
            }
        }()
        if i % 10000 == 0 {
            fmt.Printf("%d goroutine started\n", i)
        }
        i++
    }
}

协程死循环

前面我们通过 recover() 函数可以防止个别协程的崩溃波及整体进程。但是如果有个别协程死循环了会导致其它协程饥饿得到不运行么?下面我们来做一个实验

package main

import "fmt"
import "time"

func main() {
    fmt.Println("run in main goroutine")
    n := 3
    for i:=0; i<n; i++ {
        go func() {
            fmt.Println("dead loop goroutine start")
            for {}  // 死循环
        }()
    }
    for {
        time.Sleep(time.Second)
        fmt.Println("main goroutine running")
    }
}

协程的本质

一个进程内部可以运行多个线程,而每个线程又可以运行很多协程。线程要负责对协程进行调度,保证每个协程都有机会得到执行。当一个协程睡眠时,它要将线程的运行权让给其它的协程来运行,而不能持续霸占这个线程。同一个线程内部最多只会有一个协程正在运行。

640?wx_fmt=png
图片

线程的调度是由操作系统负责的,调度算法运行在内核态,而协程的调用是由 Go 语言的运行时负责的,调度算法运行在用户态。

协程可以简化为三个状态,运行态、就绪态和休眠态。同一个线程中最多只会存在一个处于运行态的协程,就绪态的协程是指那些具备了运行能力但是还没有得到运行机会的协程,它们随时会被调度到运行态,休眠态的协程还不具备运行能力,它们是在等待某些条件的发生,比如 IO 操作的完成、睡眠时间的结束等。

640?wx_fmt=png
图片

操作系统对线程的调度是抢占式的,也就是说单个线程的死循环不会影响其它线程的执行,每个线程的连续运行受到时间片的限制。

Go 语言运行时对协程的调度并不是抢占式的。如果单个协程通过死循环霸占了线程的执行权,那这个线程就没有机会去运行其它协程了,你可以说这个线程假死了。不过一个进程内部往往有多个线程,假死了一个线程没事,全部假死了才会导致整个进程卡死。

每个线程都会包含多个就绪态的协程形成了一个就绪队列,如果这个线程因为某个别协程死循环导致假死,那这个队列上所有的就绪态协程是不是就没有机会得到运行了呢?Go 语言运行时调度器采用了 work-stealing 算法,当某个线程空闲时,也就是该线程上所有的协程都在休眠(或者一个协程都没有),它就会去其它线程的就绪队列上去偷一些协程来运行。也就是说这些线程会主动找活干,在正常情况下,运行时会尽量平均分配工作任务。

设置线程数

默认情况下,Go 运行时会将线程数会被设置为机器 CPU 逻辑核心数。同时它内置的 runtime 包提供了 GOMAXPROCS(n int) 函数允许我们动态调整线程数,注意这个函数名字是全大写,Go 语言的设计者就是这么任性,该函数会返回修改前的线程数,如果参数 n <=0 ,就不会产生修改效果,等价于读操作。

package main

import "fmt"
import "runtime"

func main() {
    // 读取默认的线程数
    fmt.Println(runtime.GOMAXPROCS(0))
    // 设置线程数为 10
    runtime.GOMAXPROCS(10)
    // 读取当前的线程数
    fmt.Println(runtime.GOMAXPROCS(0))
}

--------
4
10

package main

import "fmt"
import "time"
import "runtime"

func main() {
    fmt.Println(runtime.NumGoroutine())
    for i:=0;i<10;i++ {
        go func(){
            for {
                time.Sleep(time.Second)
            }
        }()
    }
    fmt.Println(runtime.NumGoroutine())
}

------
1
11

协程的应用

在日常互联网应用中,Go 语言的协程主要应用在HTTP API 应用、消息推送系统、聊天系统等。

在 HTTP API 应用中,每一个 HTTP 请求,服务器都会单独开辟一个协程来处理。在这个请求处理过程中,要进行很多 IO 调用,比如访问数据库、访问缓存、调用外部系统等,协程会休眠,IO 处理完成后协程又会再次被调度运行。待请求的响应回复完毕后,链接断开,这个协程的寿命也就到此结束。

在消息推送系统中,客户端的链接寿命很长,大部分时间这个链接都是空闲状态,客户端会每隔几十秒周期性使用心跳来告知服务器你不要断开我。在服务器端,每一个来自客户端链接的维持都需要单独一个协程。因为消息推送系统维持的链接普遍很闲,单台服务器往往可以轻松撑起百万链接,这些维持链接的协程只有在推送消息或者心跳消息到来时才会变成就绪态被调度运行。

聊天系统也是长链接系统,它内部来往的消息要比消息推送系统频繁很多,限于 CPU 和 网卡的压力,它能撑住的连接数要比推送系统少很多。不过原理是类似的,都是一个链接由一个协程长期维持,连接断开协程也就消亡。

在后面的高级内容部分,我将会教读者使用协程来实现上面这三个系统。下一章节我们开讲通道,因为通道的使用比较复杂,知识点较多,所以需要单独一节来讲。

640?wx_fmt=png

阅读《快学 Go 语言》更多章节,长按图片识别二维码关注公众号「码洞」

Go-一个基于golang实现的协程安全的mysqlbuilder 一个基于golang实现的协程安全的mysql builder 立即下载

相关推荐

Cadence SpectreRF仿真避坑指南:PSS和Pnoise设置详解(附实战案例)

本文详细解析了Cadence SpectreRF中PSS(周期稳态分析)和Pnoise(周期噪声分析)的关键设置技巧,帮助模拟IC设计工程师避开常见仿真陷阱。通过实战案例分享,涵盖引擎选择、Beat Frequency计算、噪声类型配置等核心参数优化策略,提升仿真精度与效率。特别针对开关电容电路和振荡器等复杂场景提供专业解决方案。

weixin_42613501的博客 602

快速学习Go语言

if语句if语句用于判断某个条件是否成立,如果条件成立则执行if语句块内的代码,否则不执行。语法结构如下:// if 语句块 } else if condition2 {// else if 语句块 } else {// else 语句块 }switch语句switch语句用于根据不同的条件执行不同的代码块。语法结构如下:case value1 : // case1 语句块 case value2 : // case2 语句块 ... default : // default 语句块 }

qq_30245525的博客 2334

Applied Intelligence投稿经验分享(正在投稿中)

这几天在投稿Applied Intelligence这个期刊,过程很曲折,反反复复,因此写一篇经验分享,希望能记录一下自己的过程,也能给其他人一些帮助(可以在评论区讨论),提醒一下,一定要写一些声明,不会写、不知道写啥就看第四章的第二个问题。

weixin_55988068的博客 1万+

go语言的main函数

main函数不能带参数 main函数不能定义返回值 main函数所在的包必须为main包 main函数中可以使用flag包来获取和解析命令行参数

1万+

ComfyUI安装包运行前准备:安装Git

Git 是一个分布式版本控制系统(Version Control System, VCS),用于跟踪和管理文件(尤其是代码)的变更历史。 简单理解就是,git是用来管理文件的版本的,如果有新的版本,就可以使用git来更新。

聚梦小课堂 2万+

用Simulink复现Hopf振荡器:从数学公式到稳定极限环的保姆级仿真教程

本文详细介绍了如何在Simulink中实现Hopf振荡器,从数学原理到仿真建模的全过程。通过参数调试和波形分析,帮助读者掌握Hopf振荡器的核心特性及其在机器人控制等领域的应用。特别强调了自适应调节技巧,提升系统稳定性和鲁棒性。

weixin_29261711的博客 445

go协程实例-2

package main import ( "fmt" ) type worker struct { channel chan int done chan bool } func main() { var workers [10]worker for i:=0;i&lt;10 ;i++ { workers[i]=worker{ make(chan int), ...

qq_20867981的博客 435

abaqus 根据坐标施加载荷- Analytical Field 载荷映射

在一些类型的分析中,需要将按位置分布的载荷施加到结构上,比如流体分析导出了结构的气动载荷、或者温度载荷,需要将压强分布、温度分布载荷施加到固体上。这种分析可以使用abaqus中的Analytical Field实现。 1 例子 1.1 结构 一个100*100mm的面,面的载荷分布函数为p=(x2+y2)0.5/((20.5)*100) MPa,即(0, 0)的压强为0,(100, 100)的压强为1。 1.2 载荷 载荷函数:p=(x^2+y^2)^0.5/((2^0.5)*100) in exce.

shengyutou的博客 2万+

《快学 Go 语言》第 6 课 —— 字典

字典在数学上的词汇是映射,将一个集合中的所有元素关联到另一个集合中的部分或全部元素,并且只能是一一映射或者多对一映射。 数组切片让我们具备了可以操作一块连续内存的能力,它是对同质元素的统一管理。而字典则赋予了不连续不同类的内存变量的关联性,它表达的是一种因果关系,字典的 key 是因,字典的 value 是果。如果说数组和切片赋予了我们步行的能力,那么字...

weixin_33813128的博客 142

无线网卡选购指南:哪些型号支持Kali Linux的监听模式?附免驱动网卡推荐

本文详细解析了如何在Kali Linux下选购支持监听模式的无线网卡。核心在于选择兼容的芯片组,如Atheros AR9271和Ralink RT3070等经典型号,并提供了免驱动网卡推荐与实战配置指南,帮助安全研究人员和爱好者快速搭建无线测试环境。

wind6的博客 1555

Xilinx Aurora 8B/10B IP核详解和仿真

Xilinx Aurora 8b/10b IP核的详解,以及使用与仿真示例,对IP核的配置和如何仿真有非常详细的说明,除了对IP核官方文档的内容进行了解析以外,还对GTX引脚的选择等比较容易混淆和不好理解的地方进行了说明。最后对IP核的仿真运行进行了STEP BY STEP 的说明,对仿真的结果也进行了简要的说明。 该文档对Xilinx FPGA和ZYNQ SOC下 AURORA 8B/10B IP核的使用提供了一个快速上手的指导。

weixin_43354598的博客 1万+

千军万马跑协程goroutine

参考:https://cloud.tencent.com/developer/article/1375753 协程和通道是 Go 语言作为并发编程语言最为重要的特色之一,初学者可以完全将协程理解为线程,但是用起来比线程更加简单,占用的资源也更少。通常在一个进程里启动上万个线程就已经不堪重负,但是 Go 语言允许你启动百万协程也可以轻松应付。如果把协程比喻成小岛,那通道就是岛屿之间的...

rs勿忘初心的博客 325

qt医疗信息系统(HIS)

**UI 组件**: QWidget、QTextBrowser、QTableWidget、QGraphicsView 等。- **网络通信**: Qt Network (TCP/IP)- **DICOM 处理**: DCMTK 3.6.7。- **数据库**: SQLite (轻量、跨平台)**数据库文件**: `hospital.db`- **开发框架**: Qt 6 (C++)- 医疗影像 (DICOM) 查看器。**医疗影像 (DICOM) 查看器****电子病历 (EMR) 浏览器**

寒寒豆豆软件公司的博客 338

TIOBE编程语言全球热度排行——2020年4月流行榜,Scratch成最大亮点

TIOBE编程语言全球热度榜,4月流行榜单刚刚发布。 哪种语言是最好、最合适的编程语言,一直是程序员和企业技术选型争论不休的话题。全世界的编程语言很多,其流行度变化莫测,但是每个时期流行的不过几十种,百名之外的编程语言对使用它的程序员和企业而言生存空间已经很小。因此,随时了解和掌握编程语言的热度、受欢迎程度,关乎程序员的技术栈储备和他的职业生涯,关乎项目和产品的技术选型以及今后的生命周期。下面是...

AliveCode,Code Life 1896

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

[AI智能体与提效-154] - 在这个新时代,Python 是唯一的桥梁。它一端连接着人类温暖模糊的自然语言,另一端连接着机器冰冷精确的二进制世界。通过这座桥梁,我们终于实现了科幻小说中的愿景。

在这个新时代,Python 是唯一的桥梁。它一端连接着人类温暖模糊的自然语言,另一端连接着机器冰冷精确的二进制世界。通过这座桥梁,我们终于实现了科幻小说中的愿景:像与人交谈一样,通过自然语言指挥千军万马般的计算资源。一、跨越“语义鸿沟”:从模糊意图到精确逻辑人类的自然语言是的。当我们说“帮我分析一下这个趋势”时,这句话包含了情感、语境、隐含假设和不确定性。这种“温暖”和“模糊”是人类创造力的源泉,却是传统计算机科学的噩梦。。Python 与自然语言的句法结构高度同构。

文火冰糖(王文兵)的博客 146

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

12月3日云栖精选夜读 | Python程序员的30个常见错误

在这篇文章中,我将总结新老Python程序员常犯的一些错误,以帮助你们在自己的工作避免犯同样或类似错误。 热点热议 Python程序员的30个常见错误 作者:技术小能手发表在:大数据地盘 在阿里,不是每一个项目都有KPI 作者:技术小能手 12月1日:阿里巴巴对“父老乡亲”有话说 作者:技术小能手发表在:阿里巴巴公益 知识整理 《快学 Go 语言...

weixin_34248118的博客 270

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

可观测性体系搭建:OpenTelemetry 与 Prometheus 集成

想象你刚搬进新家,但装修时没预留监控摄像头。当水管突然爆裂或电路过载时,你只能通过砸墙找问题。这就是传统监控系统的大致状态——被动等待故障发生,而非主动预防。现代系统就像拥有千军万马的大型企业,每个服务模块都是独立运作的士兵。OpenTelemetry和Prometheus组成的可观测性体系,相当于给每个岗位配备了智能手环+电子看板。前者实时追踪员工(服务)的生理指标(性能数据),后者则汇总全局数据生成作战地图(监控面板)。

2501_92585248的博客 1227

关于Golang使用channel的死锁问题

发现问题 小白一枚,最近在入门学习Golang,在学到channel时,发现总会遇到死锁的问题: fatal error: all goroutines are asleep - deadlock! 原理解读 通过查找相关资料发现,channel本身是用于不同协程间通信的,一般一个协程作为生产者,另一个作为消费者,遵循下面两个原则: 当上一次生产到channel的数据未被消费时,生产者继...

程序猿的世界 1343

Golang百万级高并发实践

写在前面Go语言作为新兴的语言,最近发展势头很是迅猛,其最大的特点就是原生支持并发。它使用的是“协程(goroutine)模型”,和传统基于 OS 线程和进程实现不同,Go 语言的并发是基于用户态的并发,这种并发方式就变得非常轻量,能够轻松运行几万并发逻辑。Go 的并发属于 CSP 并发模型的一种实现,CSP 并发模型的核心概念是:“不要通过共享内存来通信,而应该通 过通信来共享内存”。这在 G

Jeanphorn的专栏 3万+

GO操作Mysql数据库

GO操作Mysql数据库 Go语言中的database/sql包定义了对数据库的一系列的操作。database/sql/driver包定义了对应的被数据库实现的接口。这些接口会被sql包使用,但是Go语言没有提供任何官方的数据库驱动,所以我们需要导入第三方的数据库驱动。不过我们连接数据库之后对数据库操作的大部分代码都使用sql包。 [go-sql-driver] 引入包: import ( "database/sql" "github.com/go-sql-dri...

weixin_46120107的博客 2700
上一篇: 老大难的 Java ClassLoader,到了该彻底理解它的时候了
下一篇: 码洞原创深度技术文章大全 —— 高端面试必备
码洞
博客等级 码龄10年 41粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值