欢迎关注微信公众号:壹零仓,发送关键词"代码仓库",获取示例代码!@
1.1 goroutine 原理介绍
1.2 goroutine使用方法
goroutine是go语言高并发设计的核心,是一种非常轻量级的实现,可在单个进程里执行成千上万的并发任务,它的核心是MPG调度模型。
1.1 goroutine 原理介绍
在具体说goroutine使用方法之前,先介绍下其基本原理和相关概念,如果只是想知道如何使用,直接跳到第二节。首先介绍下介个概念:并发、进程、线程和携程。
并发的概念 在很早之前,计算机只有单核心,开发语言冶金本上是按照顺序书写代码逻辑,用户一次直接能提交一个作业,计算机同时只能做一件事。但是随着计算机技术的发展,软件设计越来越复杂,以及CPU/内存/硬盘等IO速度的巨大差异造成的读写等待,这种单进程顺序执行的方式,无法满足场景应用。为了更好的利用CPU资源,一个CPU同时执行多项任务,通过CPU时间片轮询极致,实现任务之间的切换,在时间上看起来像是多任务同时执行,这就是并发。计算机的分时调用是并发的根本,CPU通过快速的切换作业来执行不同的作业,每个作业在执行的时候阻塞时,释放CPU资源,等到该调度单位再次被唤醒的时候,又可以使用CPU资源,而操作系统保证了整个的调度过程。
进程/线程/携程
进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位,进程是计算机资源分配的最小单位,是CPU分配资源的基本单位,有独立的内存。线程是进程的一个执行实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,是计算机调度的最小单位,一个进程可以有多个线程 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的,其底层基于线程池,其上有调度器,调度任务在哪个线程上执行
goroutine原理
goroutine 是 golang 实现的协程,其特点是在语言层面就支持,使用起来非常方便,它的核心是MPG调度模型(M:内核线程、P:处理器,维护本地运行队列、G:goroutine,代码和数据结构、S:调度器,维护M和P)
M会关联内核线程与进程,上下文P会有两类Goroutine,一类是正在运行的,图中的蓝色G;一类是正在排队的,图中灰色G,这个会存储在该进程中的runqueue里面。这里的上下文P的数量也表示的是Goroutinue运行的数量,可通过环境变量GOMAXPROCS的值,或者通过运行时调用函数runtime.GOMAXPROCS()进行设置。
这里做了一个简单的讲解,具体实现细节,可参照https://zhuanlan.zhihu.com/p/82740001
1.2 goroutine使用方法
golang程序中使用go关键字,加到一个函数调用的最前面,就可以实现一个goroutine,非常简单,这里要注意如果使用go创建goroutine时,函数返回值将被忽略,多个并发之间如果要实现通信,go提供了channel机制,非常好用,后续单独介绍。
使用格式:go 函数名(入参)
使用实例如下:
package main
import (
"fmt"
"time"
)
func gotest1() {
i := 0
for {
i++
fmt.Printf("gotest1 run: i = %d\n", i)
time.Sleep(3*time.Second) 延时1s
}
}
func main() {
开启一个goroutine
go gotest1()
i := 0
for {
i++
fmt.Printf("main: i = %d\n", i)
time.Sleep(3 * time.Second) 延时1s
}
}
调用 runtime.Goexit() 将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer 延迟调用被执行。
package main
import (
"fmt"
"runtime"
)
func main() {
go func() {
defer fmt.Println("1111")//函数退出时执行,相当于c++的析构函数
func() {
defer fmt.Println("2222")
runtime.Goexit() 终止当前 goroutine
fmt.Println("3333")
}()
fmt.Println(44444")
}() //匿名函数的写法
for {
}
}
执行结果为 2222 1111
欢迎关注微信公众号:壹零仓,发送关键词"代码仓库",获取示例代码!