goroutine

本文介绍了Go语言中goroutine的基本概念,包括并发、进程、线程与协程的区别,并详细解析了goroutine如何通过自身调度器实现高效并发,以及goroutine间的通信方式和安全退出机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.goroutine

goroutine是go语言的并发体。在go语言里面能使用go关键字来实现并发。


 

1.1 概念介绍

goroutine本质上是协程,我刚刚学习的时候就粗略地认为goroutine是线程,直到最近才开始搞明白goroutine的基本概念。

并发

在很久以前,人们希望一个计算机(一个cpu)上能同时执行多项任务,让cpu在某段时间内进行分片,在某段很短时间内执行程序a,然后又迅速得切换到程序b去执行,让人们看起来就像是两个程序在同时进行,这就是并发

进程

但是人们随之发现,cpu在切换程序的时候,如果不保存上一个程序的状态(也就是我们常说的context--上下文),直接切换下一个程序,就会丢失上一个程序的一系列状态,于是引入了进程这个概念,用以划分好程序运行时所需要的资源。因此进程就是一个程序运行时候的所需要的基本资源单位(也可以说是程序运行的一个实体)。

并行

如果一个电脑有多个cpu,每个cpu都有进程在运行,这就是并行。

用户态与内核态

为了防止用户程序做出一些危险的指令,如关机,更改系统变量,修改别的进程数据,系统分为两种运行状态,用户态以及内核态,用户态是我们的程序所在的状态,不能随便对内核的底层进行操作。如果我们需要使用内核的底层操作的时候,内核提供了一种调用内核的接口,我们调用这些接口也就是系统调用,在进行系统调用的时候,cpu会切换到内核态,才能执行内核的函数。

线程

人们又发现一个问题,cpu切换多个进程的时候,会花费不少的时间,因为切换进程需要切换到内核态,而每次调度需要内核态都需要读取用户态的数据,进程一旦多起来,cpu调度会消耗一大堆资源,因此引入了线程的概念,线程本身几乎不占有资源,他们共享进程里的资源,内核调度起来不会那么像进程切换那么耗费资源。

协程

但是线程还是需要内核去进行调度,切换起来也是需要把用户态的数据写入到内核态,也是需要耗费一定的计算机资源,那可以不可以将切换的调度改成我们自己控制的呢,答案是有的,协程就是把自己的调度算法交给程序(用户态)去进行管理,能以更小的资源去进行并发。

goruntine

goroutine就是一个协程例子,可以根据自身调度器进行调度,当某个gooutine调用了time.sleep方法或者channel,mutex阻塞时候,调度器会使其入睡,唤醒另一个goroutine,根本不需要进入到内核态。

2.通信

goroutine本质上是协程,可以理解为不受内核调度,而受go调度器管理的线程。goroutine之间可以通过channel进行通信,如下:


 

3.安全退出

goroutine只有在自身所在函数运行完毕,或者主函数运行完毕才会打断,因而上面的例子需要等待一秒,不然未执行完的goroutine会直接被打断。 如果我们并发的线程数量多了之后,我们不可能在main里面设置一个精确睡眠时间来评估所有的goroutine已经运行完毕然后退出。
这时候我们可以使用sync.WaitGroup来等待所有运行的goroutine运行结束后,再来退出main函数,主要原理是维护一个goroutine数量的计数器,每运行一个goroutine,计数器会加+1,运行结束后,计数器会-1,然后调用wait方法会一直阻塞,知道计数器为0,也就是当前运行的goroutine数量为0,实例如下:


 
### Goroutine 工作机制 在 Go 语言中,Goroutine 是一种轻量级的线程管理机制[^2]。当函数前加上 `go` 关键字时,该函数就会在一个新的 Goroutine 中异步执行。Go 运行时会自动管理和调度这些 Goroutine。 #### 创建和销毁成本低 相比于传统操作系统级别的线程,Goroutine 的创建和销毁代价极小。每个 Goroutine 只需大约 4~5 KB 的栈空间,并且可以根据需要动态调整大小。这种高效的资源利用方式使得可以在单个进程中轻松启动成千上万个 Goroutine 来处理并发任务[^3]。 #### 调度器的作用 Go 运行时内部有一个专门的任务调度器负责分配 CPU 时间给各个活跃的 Goroutine。这个调度器能够根据当前系统的负载情况智能地平衡各 Goroutine 执行时间片,从而保证整体性能最优[^1]。 --- ### 使用方法示例 下面是一个简单的例子展示了如何使用 goroutines 实现基本的并发操作: ```go package main import ( "fmt" "time" ) func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") // 启动一个新的goroutine去执行say函数 say("hello") // 主goroutine继续在这里阻塞直到完成自己的工作 } ``` 在这个例子中,`main()` 函数启动了一个新 goroutine 来打印 "world" 字符串五次的同时自己也打印了 "hello"[^4]。 需要注意的是,在上面的例子中如果希望等待子 goroutine 完成后再退出程序,则可以通过 WaitGroup 或者 Channel 等同步原语来实现这一点。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值