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

Golang协程

目录

进程、线程、多线程、协程、用户态、内核态的概念

核心场景:把计算机想象成一个 「大商场」

1. 进程 (Process)

2. 线程 (Thread)

3. 多线程 (Multithreading)

4. 协程 (Coroutine) / Goroutine

5. 用户态 (User Space) vs 内核态 (Kernel Space)

关系总结(一张图看懂)

终极比喻

Goroutine

场景:处理一个工作任务(比如,处理100份文件)

1. 传统多线程(其他语言,比如 Java/C++)

2. Go 语言的 Goroutine

核心总结:

一句话终极比喻:

Go关键字实现协程

创建协程

用协程创建一个形参为空,返回值为空的函数

用协程创建一个有形参,有返回值的函数


在学习协程之前要先了解下面几个词的概念,由ai生成比较通俗易懂的解释:

进程、线程、多线程、协程、用户态、内核态的概念

核心场景:把计算机想象成一个 「大商场」


1. 进程 (Process)
  • 比喻: 商场里的一个独立店铺

    • 比如一家餐厅、一家服装店、一家书店。

  • 特点:

    • 资源独立: 每个店铺都有自己的空间(内存)、收银台(资源)、员工(线程)。餐厅不会用书店的厨房,服装店不会卖书店的书。

    • 相互隔离: 一个店铺着火(崩溃)了,一般不会影响其他店铺。

    • 切换成本高: 从一个店铺跑到另一个店铺,需要花点时间(上下文切换开销大)。


2. 线程 (Thread)
  • 比喻: 店铺里的一个员工

    • 餐厅里有厨师、服务员、收银员。

  • 特点:

    • 共享资源: 同一个店铺的所有员工,共享这个店铺的厨房、收银台、餐桌(共享进程的内存和资源)。

    • 协作工作: 员工们可以同时做不同的事(并发),比如厨师炒菜,服务员端盘子。

    • 是执行任务的基本单位: 活最终是靠员工(线程)来干的。


3. 多线程 (Multithreading)
  • 比喻: 一个店铺里雇佣了多个员工同时工作

    • 餐厅同时有3个厨师炒菜,5个服务员接待客人。

  • 特点:

    • 提高效率: 可以同时服务更多顾客(处理更多任务),店铺(进程)的吞吐量变大。

    • 需要管理: 员工之间需要协调,比如不能两个厨师抢同一个锅(需要机制)。

    • 创建和管理有开销: 雇佣正式员工(创建线程)需要办手续,发工资(占用系统资源)。


4. 协程 (Coroutine) / Goroutine
  • 比喻: 一个「超级实习生」或「兼职学生」

    • 他不是一个正式员工(线程),但能干员工的活。

  • 特点:

    • 极低成本: 雇佣一个实习生(创建协程)手续非常简单,成本极低(内存占用KB级别)。你可以轻松雇几百个实习生。

    • 超级灵活: 当某个实习生需要等货(等待IO,比如等网络数据),他会立刻主动让出位置,去干别的活,绝不摸鱼。

    • 由店长管理: 这些实习生不由商场经理(操作系统)直接管理,而是由你们店铺自己的店长(用户态调度器,如Go的调度器)来调度。


5. 用户态 (User Space) vs 内核态 (Kernel Space)

这是理解为什么协程更高效的关键!

  • 内核态 (Kernel Space): 商场总管理处

    • 这是商场的核心权力机构,拥有最高权限,管理整个商场的水电、安全、所有店铺的协调。

    • 特点: 权力大,但进去办事流程复杂,速度慢。

    • 对应操作: 线程的创建、销毁、调度(哪个线程在哪个CPU核心上运行)都需要「总管理处」亲自审批和操作。这是一个很「重」的操作。

  • 用户态 (User Space): 你自己的店铺内部

    • 这是你的地盘,你在自己店铺里怎么安排工作,自己说了算。

    • 特点: 权限小,但效率高,非常灵活。

    • 对应操作: 协程的创建、销毁、调度都是在你的程序内部(你的店铺里)完成的,不需要惊动「总管理处」。这是一个很「轻」的操作。

关键区别:

  • 创建一个正式员工(线程)=> 需要向商场总管理处(内核态) 打报告、走流程,开销大。

  • 创建一个实习生(协程)=> 只需要店长(用户态调度器) 在自己店里安排一下,开销极小。


关系总结(一张图看懂)

text

商场 (计算机)
|
├── 店铺A (进程A)
│   ├── 正式员工1 (线程1) -- 由【商场总管理处】(内核态)调度
│   ├── 正式员工2 (线程2) -- 由【商场总管理处】(内核态)调度
│   └── 一群实习生 (协程) -- 由【店长】(用户态调度器)管理,跑在几个正式员工身上
|
├── 店铺B (进程B)
│   └── ...

流程演进:

  1. 早期: 一个店铺只有一个员工(单线程进程),效率低。

  2. 发展: 一个店铺雇多个员工(多线程进程),效率高了,但雇佣和管理成本(创建/切换线程的开销)也高了。

  3. 现代(Go的方案): 店铺只雇少数几个核心正式员工(少数几个线程),但雇佣一大群成本极低、极其灵活的「超级实习生」(Goroutine)。店长(Go调度器)在自己店里(用户态)高效地安排这些实习生的工作,让他们充分压榨几个正式员工的能力,从而用极低的成本实现了极高的并发。

终极比喻

  • 进程 = 一个独立的公司/店铺

  • 线程 = 公司里的正式员工

  • 多线程 = 公司里雇了很多正式员工一起干活

  • 协程 = 公司里雇了一大群「超级实习生」,成本低,效率高,由部门经理直接管理

  • 用户态 = 公司内部管理

  • 内核态 = 政府/工商局管理


Goroutine

你可以把 Goroutine 想象成 “公司里的员工”

我们来对比一下传统的“多线程编程”和 Go 语言的“Goroutine”。

场景:处理一个工作任务(比如,处理100份文件)


1. 传统多线程(其他语言,比如 Java/C++)
  • 比喻: 这就像是雇佣正式员工

  • 特点:

    • 成本高: 每雇佣一个正式员工(创建一个线程),公司都要付出很高的成本(占用大量内存和系统资源)。

    • 管理难: 你不能随便雇佣成百上千个正式员工,否则人力资源部(操作系统)会忙不过来,公司(你的程序)也会被拖垮。

    • 工作方式: 一个员工处理一份文件,如果文件很多,你只能开几个线程,让它们排队等着操作系统来调度。

结果: 虽然也能多人同时工作(并发),但员工数量(线程数)有限,创建和管理的开销都很大。


2. Go 语言的 Goroutine
  • 比喻: 这就像是雇佣“超级实习生”

  • 特点:

    • 成本极低: 创建一个 Goroutine 只需要极小的内存(初始栈只有 2KB),相当于雇一个实习生的成本几乎可以忽略不计。你轻松就能启动成千上万个 Goroutine。

    • 一个“经理”管理所有实习生: Go 语言运行时自带一个“调度经理”(Scheduler)。这个经理只占用几个正式员工的名额(只跑在几个操作系统线程上),但他非常聪明,负责管理成千上万个“超级实习生”(Goroutine)。

    • 超级高效: 当一个实习生(比如 G1)在等待(比如等硬盘读取文件),经理就会立刻让他休息,并把 CPU 资源分配给另一个准备好干活的实习生(G2)。这个切换速度非常非常快,就像经理在不停地给实习生们分配微小的任务一样。

    • 协作式: 实习生们很懂事,会在适当的时候(比如遇到等待操作时)主动告诉经理:“经理,我先歇会,你去让别人干吧!” 这大大减少了管理的摩擦。

结果: 你可以轻松发动“人海战术”,派出成千上万个成本极低的“超级实习生”去处理海量的小任务,而且有一个聪明的“经理”在背后高效调度,保证 CPU 永远在干活,而不是在空闲等待。


核心总结:

  1. Goroutine 是什么?

    • 它是 Go 语言中的轻量级线程。你可以把它理解为一个超级便宜、高效的“工作任务执行者”。

  2. 它为什么牛?

    • 轻量: 资源占用极小,可以轻松创建上百万个。

    • 高效的调度: 由 Go 语言自身的调度器管理,而不是完全依赖操作系统,切换成本非常低。

    • 简单的语法: 你只需要在普通的函数调用前加一个 go 关键字,就能让这个函数在一个新的 Goroutine 中并发执行。

    // 普通函数调用,会按顺序执行
    processFile("file1.txt")
    processFile("file2.txt") // 等上一行执行完才执行// 使用 Goroutine,两个函数调用会并发执行
    go processFile("file1.txt") // 丢给一个“实习生”去做
    go processFile("file2.txt") // 再丢给另一个“实习生”去做
    // 主函数会继续向下执行,不会等待它们
  3. 你需要关心什么?

    • 当你有成千上万个“实习生”在同时处理数据时,你需要注意他们之间的通信和协作。比如,两个实习生不能同时修改同一份数据,否则会乱套。这就是为什么 Go 语言提供了 Channel(通道) 这个强大的工具,让 Goroutine 之间可以安全、有序地传递数据和同步状态。

    • Channel 就像一个传送带或者公司内部的工作交接区。一个实习生把处理完的结果放到传送带上,另一个实习生从传送带上取走继续处理。这样既完成了协作,又避免了冲突。

一句话终极比喻:

Goroutine 就是让你能用“开一个线程”的成本,去发动一场“千军万马”的并发任务。

Go关键字实现协程

创建协程

可以看到主线程和协程是并行执行的,不分先后

package mainimport ("fmt""time"
)func newTask() {i := 0for {i++fmt.Printf("new Goroutine: i = %d\n", i)time.Sleep(1 * time.Second)}
}func main() {//创建协程执行newTask函数go newTask()i := 0for {i++fmt.Printf("main Goroutine: i = %d\n", i)time.Sleep(1 * time.Second)}
}------------------------------------------------PS D:\GoProject\firstGoProject> go run firstGoProject.go
main Goroutine: i = 1
new Goroutine: i = 1
new Goroutine: i = 2
main Goroutine: i = 2
main Goroutine: i = 3
new Goroutine: i = 3
new Goroutine: i = 4
main Goroutine: i = 4
main Goroutine: i = 5
new Goroutine: i = 5
new Goroutine: i = 6
main Goroutine: i = 6
main Goroutine: i = 7
new Goroutine: i = 7
new Goroutine: i = 8
main Goroutine: i = 8
main Goroutine: i = 9
new Goroutine: i = 9
new Goroutine: i = 10
main Goroutine: i = 10
main Goroutine: i = 11
new Goroutine: i = 11
new Goroutine: i = 12
main Goroutine: i = 12

用协程创建一个形参为空,返回值为空的函数

退出协程需要通过 runtime.Goexit() 来操作

package mainimport ("fmt""time"
)func main() {//用go创建承载一个形参为空,返回值为空的一个函数go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")fmt.Println("B")}()fmt.Println("A")}()//死循环for {time.Sleep(1 * time.Second)}
}--------------------------------------------------
PS D:\GoProject\firstGoProject> go run firstGoProject.go
B
B.defer
A      
A.defer===============================================================================
func main() {//用go创建承载一个形参为空,返回值为空的一个函数go func() {defer fmt.Println("A.defer")//退出当前协程return//下面代码不会被执行func() {defer fmt.Println("B.defer")fmt.Println("B")}()fmt.Println("A")}()//死循环for {time.Sleep(1 * time.Second)}
}--------------------------------------------------
PS D:\GoProject\firstGoProject> go run firstGoProject.go     
A.defer===============================================================================
func main() {//用go创建承载一个形参为空,返回值为空的一个函数go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")//尝试在子函数中退出协程return   //实际上只会退出当前子函数,不会退出协程fmt.Println("B")}()fmt.Println("A")}()//死循环for {time.Sleep(1 * time.Second)}
}--------------------------------------------------
PS D:\GoProject\firstGoProject> go run firstGoProject.go
B.defer
A      
A.defer===============================================================================
func main() {//用go创建承载一个形参为空,返回值为空的一个函数go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")//在go协程的子函数退出整个协程操作runtime.Goexit()fmt.Println("B")}()fmt.Println("A")}()//死循环for {time.Sleep(1 * time.Second)}
}--------------------------------------------------
PS D:\GoProject\firstGoProject> go run firstGoProject.go
B.defer
A.defer

用协程创建一个有形参,有返回值的函数

package mainimport ("fmt""time"
)func main() {//用go创建一个有形参,有返回值的函数//注意这里协程和main函数是异步执行的,在main函数中并不能通过定义一个bool类型拿到返回值go func(a int, b int) bool {fmt.Println("a = ", a, "b = ", b)return true}(10, 20)//死循环for {time.Sleep(1 * time.Second)}
}--------------------------------------PS D:\GoProject\firstGoProject> go run firstGoProject.go
a =  10 b =  20

http://www.dtcms.com/a/477983.html

相关文章:

  • 深圳网站建设网站dw学生个人网页制作视频
  • 深度强化学习 | 基于SAC算法的动态避障(ROS C++仿真)
  • 智能美甲灯方案,UV/LED美甲光疗机美甲烤灯MCU控制方案开发设计
  • 用html5写一个可输入1-100行1-100列的矩阵计算器
  • 如何在第三方网站做推广湖北建设注册中心网站首页
  • 福州网站建设信息百度推广账号登陆入口
  • 纯知识干货java学习之问答一
  • L05_后端_MinIO 安装使用入门指南(实战版)
  • [ SpringBoot ]
  • Nginx 负载均衡调度算法
  • 全链路Controller压测负载均衡
  • FastCRUD:为 FastAPI 量身打造的现代化异步 CRUD 框架,让后端开发更高效
  • 宠物服务到店预约/宠物服务上门预约/商城零售o2o
  • 网站做推广企业wordpress最新模板
  • 云手机长期使用会消耗很多流量吗
  • 自己做的网站服务器开了进不去网站定位要点 有哪些方面
  • figma-developer-mcp
  • BUG() 和 BUG_ON()
  • 牛客周赛 Round 111
  • Vue 3 watch 与 watchEffect ,哪个更好?
  • 建工网站响应式网站发展
  • 企业网站pc优化网站的基本结构
  • 电子静止质量 Electron rest mass
  • DB-GPT实现Text2SQL全流程解析
  • 数据结构--------顺序表
  • 【完整源码+数据集+部署教程】硅藻分类识别系统源码和数据集:改进yolo11-DRBNCSPELAN
  • 【04】VisionMaster入门到精通——模板匹配【高精度匹配、快速匹配】
  • cv里的图像分割任务的部分评价指标
  • 电子商城网站建设与维护桂林市网站建设公司
  • PMP-项目管理-PMBOK第六版_中文版:图表工具