并发与并行,同步和异步,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang并发编程之GoroutineEP13
如果说Go lang是静态语言中的皇冠,那么,Goroutine就是并发编程方式中的钻石。Goroutine是Go语言设计体系中最核心的精华,它非常轻量,一个 Goroutine 只占几 KB,并且这几 KB 就足够 Goroutine 运行完,这就能在有限的内存空间内支持大量 Goroutine协程任务,方寸之间,运筹帷幄,用极少的成本获取最高的效率,支持了更多的并发,毫无疑问,Goroutine是比Python的协程原理事件循环更高级的并发异步编程方式。
GMP调度模型(Goroutine-Machine-Processor)
为什么Goroutine比Python的事件循环高级?是因为Go lang的调度模型GMP可以参与系统内核线程中的调度,这里G为Goroutine,是被调度的最小单元;M是系统起了多少个线程;P为Processor,也就是CPU处理器,调度器的核心处理器,通常表示执行上下文,用于匹配 M 和 G 。P 的数量不能超过 GOMAXPROCS 配置数量,这个参数的默认值为当前电脑的总核心数,通常一个 P 可以与多个 M 对应,但同一时刻,这个 P 只能和其中一个 M 发生绑定关系;M 被创建之后需要自行在 P 的 free list 中找到 P 进行绑定,没有绑定 P 的 M,会进入阻塞状态,每一个P最多关联256个G。
说白了,就是GMP和Python一样,也是维护一个任务队列,只不过这个任务队列是通过Goroutine来调度,怎么调度?通过Goroutine和系统线程M的协商,寻找非阻塞的通道,进入P的本地小队列,然后交给系统内的CPU执行,藉此,充分利用了CPU的多核资源。
而Python的协程方式仅仅停留在用户态,它没法参与到线程内核的调度,弥补方式是单线程多协程任务下开多进程,Go lang则是全权交给Goroutine,用户不需要参与底层操作,同时又可以利用CPU的多核资源。
启动Goroutine
首先默认情况下,golang程序还是由上自下的串行方式:
package main
import (
"fmt"
)
func job() {
fmt.Println("任务执行")
}
func main() {
job()
fmt.Println("任务执行完了")
}
程序返回:
任务执行
任务执行完了
这里job中的打印函数是先于main中的打印函数。
现在,在执行job函数前面加上关键字go,也就是启动一个goroutine去执行job这个函数:
package main
import (
"fmt"
"time"
)
func job() {
fmt.Println("任务执行")
}
func main() {
go job()
fmt.Println("任务执行完了")
time.Sleep(time.Second)
}
注意,开启Goroutine是在函数执行的时候开启,并非声明的时候,程序返回:
任务执行完了
任务执行
可以看到,执行顺序颠倒了过来,首先为什么会先打印任务执行完了,是因为系统在创建新的Goroutine的时候需要耗费一些资源,因为就算只有几kb,也需要时间来创建,而此时main函数所在的goroutine是继续执行的。
第二,为什么要人为的把main函数延迟一秒钟?
因为当main()函数返回的时候main所在的Goroutine就结束了,所有在main()函数中启动的goroutine会一同结束,所以这里必须人为的“阻塞”一下main函数,让它后于job结束,有点像公园如果要关门必须等最后一个游客走了才能关,否则就把游客关在公园里了,出不去了。
与此同时,此逻辑和Python中的线程阻塞逻辑非常一致,用过Python多线程的朋友肯定知道要想让所有子线程都执行完毕,必须阻塞主线程,不能让主线程提前执行完,这和Goroutine有异曲同工之妙。
在Go lang中实现并发编程就是如此轻松,我们还可以启动多个Goroutine:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func job(i int) {
defer wg.Done() // 协程结束就通知
fmt.Println("协程任务执行", i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // 启动协程任务后入队
go job(i)
}
wg.Wait() // 等待所有登记的goroutine都结束
fmt.Println("所有任务执行完毕")
}
程序返回:
协程任务执行 8
协程任务执行 9
协程任务执行 5
协程任务执行 0
协程任务执行 1
协程任务执行 4
协程任务执行 7
协程任务执行 2
协程任务执行 3
协程任务执行 6
所有任务执行完毕
这里我们摒弃了相对土鳖的time.Sleep(time.Second)方式,而是采用sync包的WaitGroup方式,原理是当启动协程任务后,在WaitGroup登记,当每个协程任务执行完成后,通知WaitGroup,直到所有的协程任务都执行完毕,然后再执行main函数所在的协程,所以“所有任务执行完毕”会在所有协程任务执行完毕后再打印。
和Python协程区别
我们再来看看,如果是Python,会怎么做?
import asyncio
import random
async def job(i):
print("协程任务执行{}".format(i))
await asyncio.sleep(random.randint(1,5))
print("协程任务结束{}".format(i))
async def main():
tasks = [asyncio.create_task(job(i)) for i in range(10)]
res = await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(main())
程序返回:
协程任务执行0
协程任务执行1
协程任务执行2
协程任务执行3
协程任务执行4
协程任务执行5
协程任务执行6
协程任务执行7
协程任务执行8
协程任务执行9
协程任务结束0
协程任务结束1
协程任务结束3
协程任务结束6
协程任务结束9
协程任务结束8
协程任务结束2
协程任务结束4
协程任务结束5
协程任务结束7
可以看到,Python协程工作的前提是,必须在同一个事件循环中,同时逻辑内必须由用户来手动切换,才能达到“并发”的工作方式,假设,如果我们不手动切换呢?
import asyncio
import random
async def job(i):
print("协程任务执行{}".format(i))
print("协程任务结束{}".format(i))
async def main():
tasks = [asyncio.create_task(job(i)) for i in range(10)]
res = await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(main())
程序返回:
协程任务执行0
协程任务结束0
协程任务执行1
协程任务结束1
协程任务执行2
协程任务结束2
协程任务执行3
协程任务结束3
协程任务执行4
协程任务结束4
协程任务执行5
协程任务结束5
协程任务执行6
协程任务结束6
协程任务执行7
协程任务结束7
协程任务执行8
协程任务结束8
协程任务执行9
协程任务结束9
一望而知,只要你不手动切任务,它就立刻回到了“串行”的工作方式,同步的执行任务,那么协程的意义在哪儿呢?
所以,归根结底,Goroutine除了可以极大的利用系统多核资源,它还能帮助开发者来切换协程任务,简化开发者的工作,说白了就是,不懂协程工作原理,也能照猫画虎写go lang代码,但如果不懂协程工作原理的前提下,写Python协程并发逻辑呢?恐怕够呛吧。
结语
综上,Goroutine的工作方式,就是多个协程在多个线程上切换,既可以用到多核,又可以减少切换开销。但有光就有影,有利就有弊,Goroutine确实不需要开发者过度参与,但这样开发者就少了很多自由度,一些定制化场景下,就只能采用单一的Goroutine手段,比如一些纯IO密集型任务场景,像爬虫,你有多少cpu的意义并不大,因为cpu老是等着你的io操作,所以Python这种协程工作方式在纯IO密集型任务场景下并不逊色于Goroutine。
并发与并行,同步和异步,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang并发编程之GoroutineEP13的更多相关文章
- 延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17
先行定义,延后执行.不得不佩服Go lang设计者天才的设计,事实上,defer关键字就相当于Python中的try{ ...}except{ ...}finally{...}结构设计中的finall ...
- 仙人指路,引而不发,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中New和Make函数的使用背景和区别EP16
Golang只有二十五个系统保留关键字,二十几个系统内置函数,加起来只有五十个左右需要记住的关键字,纵观编程宇宙,无人能出其右.其中还有一些保留关键字属于"锦上添花",什么叫锦上添 ...
- 清源正本,鉴往知来,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中引用类型是否进行引用传递EP18
开篇明义,Go lang中从来就不存在所谓的"引用传递",从来就只有一种变量传递方式,那就是值传递.因为引用传递的前提是存在"引用变量",但是Go lang中从 ...
- 你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06
再续前文,在面向对象层面,Python做到了超神:万物皆为对象,而Ruby,则干脆就是神:飞花摘叶皆可对象.二者都提供对象类操作以及继承的方式为面向对象张目,但Go lang显然有一些特立独行,因为它 ...
- 百亿数据百亿花, 库若恒河沙复沙,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang数据库操作实践EP12
Golang可以通过Gorm包来操作数据库,所谓ORM,即Object Relational Mapping(数据关系映射),说白了就是通过模式化的语法来操作数据库的行对象或者表对象,对比相对灵活繁复 ...
- 兔起鹘落全端涵盖,Go lang1.18入门精炼教程,由白丁入鸿儒,全平台(Sublime 4)Go lang开发环境搭建EP00
Go lang,为并发而生的静态语言,源于C语言又不拘泥于性能,高效却不流于古板,Python灵活,略输性能,Java严谨,稍逊风骚.君不见各大厂牌均纷纷使用Go lang对自己的高并发业务进行重构, ...
- 化整为零优化重用,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang函数的定义和使用EP07
函数是基于功能或者逻辑进行聚合的可复用的代码块.将一些复杂的.冗长的代码抽离封装成多个代码片段,即函数,有助于提高代码逻辑的可读性和可维护性.不同于Python,由于 Go lang是编译型语言,编译 ...
- 层次分明井然有条,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang包管理机制(package)EP10
Go lang使用包(package)这种概念元素来统筹代码,所有代码功能上的可调用性都定义在包这个级别,如果我们需要调用依赖,那就"导包"就行了,无论是内部的还是外部的,使用im ...
- 因势而变,因时而动,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang泛型(generic)的使用EP15
事实上,泛型才是Go lang1.18最具特色的所在,但为什么我们一定要拖到后面才去探讨泛型?类比的话,我们可以想象一下给小学一年级的学生讲王勃的千古名篇<滕王阁序>,小学生有多大的概率可 ...
随机推荐
- 【RocketMQ】NameServer的启动
NameServer是一个注册中心,Broker在启动时向所有的NameServer注册,生产者Producer和消费者Consumer可以从NameServer中获取所有注册的Broker列表,并从 ...
- kali 漏洞扫描
前言 漏洞扫描器是一种能够自动在计算机.信息系统.网络及应用软件中寻找和发现安全弱点的程序.它通过网络对目录系统进行探测,向目标系统发送数据,并将反馈数据与自带的漏洞特征库进行匹配,进而列举目标系统上 ...
- 再测云原生数据库性能:PolarDB依旧最强,TDSQL-C、GaussDB变化不大
1.摘要 近期,腾讯云数据库在文章「腾讯云TDSQL-C重磅升级,性能全面领跑云原生数据库市场」中提到,某些场景下性能有非常大的提升,且超过国内某橙色云厂商.恰好,在5月份,我们对各个厂商的云原生数据 ...
- nginx启动失败/报错(bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a socket...permissions) nginx启动失败
出现这个问题是因为80端口被占用了 1.cmd输入命令netstat -aon|findstr "80" 2..查看80端口 16356对应的任务 输入命令 tasklist|fi ...
- jvm jni 及 pvm pybind11 大批量数据传输及优化
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- Day04 HTML标记
路径 ./ 同级目录 ./ 进入该目录名下 ../ 上一级目录 HTML标记 图片 <!-- 图片标记 src 图片的路径 width 设置图片宽度 height 设置图片高度 title 鼠标 ...
- 基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- Tomcat深入浅出(一)
一.Tomcat简介 我们下载好Tomcat后需要配置一下Java环境:如果打开出现闪退得情况,首先是jdk 同时配置JRE_HOME Tomcat的一些关键目录: /bin:存放用于启动及关闭的文件 ...
- Gorgerous -「歌词」留言板
歌者,献祭「心声」,以沐浴「新生」. Oh love. How I miss you every single days when I see you on those streets. Oh lov ...
- ES6中class方法及super关键字
ES6 class中的一些问题 记录下class中的原型,实例,super之间的关系 //父类 class Dad { constructor(x, y) { this.x = 5; this.y = ...