Go CSP并发模型Go的CSP并发模型
Go实现了两种并发形式 。第一种是大家普遍认知的:多线程共享内存 。其实就是Java或者C等语言中的多线程开发 。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型 。
CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程 , 是一种并发编程模型,由 Tony Hoare 于 1977 年提出 。简单来说 , CSP 模型由并发执行的实体(线程或者进程)所组成,实体之间通过发送消息进行通信,这里发送消息时使用的就是通道,或者叫 channel 。CSP 模型的关键是关注 channel,而不关注发送消息的实体 。Go 语言实现了 CSP 部分理论。
“不要以共享内存的方式来通信,相反,要通过通信来共享内存 。”
Go的CSP并发模型 , 是通过goroutine和channel来实现的 。
goroutine 是Go语言中并发的执行单位 。其实就是协程 。
channel是Go语言中各个并发结构体(goroutine)之前的通信机制 。通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道 。
Channel
Goroutine
Golang入门到项目实战 | golang并发变成之通道channel Go提供了一种称为通道的机制,用于在goroutine之间共享数据 。当您作为goroutine执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换 。
根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道 。无缓冲通道用于执行goroutine之间的同步通信,而缓冲通道用于执行异步通信 。无缓冲通道保证在发送和接收发生的瞬间两个goroutine之间的交换 。缓冲通道没有这样的保证 。
【go语言处理并发请求 go语言并行】 通道由make函数创建,该函数指定chan关键字和通道的元素类型 。
这是创建无缓冲和缓冲通道的代码块:
语法
使用内置函数make创建无缓冲和缓冲通道 。make的第一个参数需要关键字chan,然后是通道允许交换的数据类型 。
这是将值发送到通道的代码块需要使用-运算符:
语法
一个包含5个值的缓冲区的字符串类型的goroutine1通道 。然后我们通过通道发送字符串“Australia” 。
这是从通道接收值的代码块:
语法
- 运算符附加到通道变量(goroutine1)的左侧,以接收来自通道的值 。
在无缓冲通道中,在接收到任何值之前没有能力保存它 。在这种类型的通道中,发送和接收goroutine在任何发送或接收操作完成之前的同一时刻都准备就绪 。如果两个goroutine没有在同一时刻准备好,则通道会让执行其各自发送或接收操作的goroutine首先等待 。同步是通道上发送和接收之间交互的基础 。没有另一个就不可能发生 。
在缓冲通道中,有能力在接收到一个或多个值之前保存它们 。在这种类型的通道中,不要强制goroutine在同一时刻准备好执行发送和接收 。当发送和接收阻塞时也有不同的条件 。只有当通道中没有要接收的值时 , 接收才会阻塞 。仅当没有可用缓冲区来放置正在发送的值时,发送才会阻塞 。
实例
运行结果
go语言--Goroutines1、goroutine:在go语言中,每一个并发的执行单元叫做goroutine,如果一个程序中包含多个goroutine,对两个函数的调用则可能发生在同一时刻
2、main goroutine:当一个程序启动时 , 其主函数即在一个单独的goroutine中运行,我们叫他为main gorountine
3、go goroutine:新的goroutine会用go语句来创建,go 函数名,go语句会使其语句中的函数在一新创建的goroutine中运行,而go语句本身会迅速地完成
4、goroutine的退出:主函数返回时,所有的goroutine都会被直接打断,程序退出,除了从主函数退出或者终止程序之外 , 没有其他方法能够让一个goroutine来打断另一个的执行 , 但是可以通过另一种方式来实现这个目的,通过goroutine之间的通信来让一个goroutine请求其他的goroutine,并让请求的goroutine自行结束执行
【golang】高并发下TCP常见问题解决方案首先,看一下TCP握手简单描绘过程:
其握手过程原理,就不必说了 , 有很多详细文章进行叙述,本文只关注研究重点 。
在第三次握手过程中,如果服务器收到ACK , 就会与客户端建立连接,此时内核会把连接从半连接队列移除 , 然后创建新的连接,并将其添加到全连接队列,等待进程调用 。
如果服务器繁忙,来不及调用连接导致全连接队列溢出,服务器就会放弃当前握手连接,发送RST给客户端,即connection reset by peer 。
在linux平台上,客户端在进行高并发TCP连接处理时,最高并发数量都要受系统对用户单一进程同时打开文件数量的限制(这是因为系统每个TCP都是SOCKET句柄 , 每个soker句柄都是一个文件),当打开连接超过限制,就会出现too many open files 。
使用下指令查看最大句柄数量:
增加句柄解决方案
Go语言——goroutine并发模型参考:
Goroutine并发调度模型深度解析手撸一个协程池
Golang 的 goroutine 是如何实现的?
Golang - 调度剖析【第二部分】
OS线程初始栈为2MB 。Go语言中go语言处理并发请求,每个goroutine采用动态扩容方式go语言处理并发请求,初始2KB,按需增长,最大1G 。此外GC会收缩栈空间 。
BTW,增长扩容都是有代价的,需要copy数据到新的stack,所以初始2KB可能有些性能问题 。
更多关于stack的内容,可以参见大佬的文章 。聊一聊goroutine stack
用户线程的调度以及生命周期管理都是用户层面,Go语言自己实现的,不借助OS系统调用,减少系统资源消耗 。
Go语言采用两级线程模型,即用户线程与内核线程KSE(kernel scheduling entity)是M:N的 。最终goroutine还是会交给OS线程执行,但是需要一个中介,提供上下文 。这就是G-M-P模型
Go调度器有两个不同的运行队列:
go1.10\src\runtime\runtime2.go
Go调度器根据事件进行上下文切换 。
调度的目的就是防止M堵塞,空闲,系统进程切换 。
详见Golang - 调度剖析【第二部分】
Linux可以通过epoll实现网络调用,统称网络轮询器N(Net Poller) 。
文件IO操作
上面都是防止M堵塞,任务窃取是防止M空闲
每个M都有一个特殊的G,g0 。用于执行调度,gc,栈管理等任务,所以g0的栈称为调度栈 。g0的栈不会自动增长,不会被gc,来自os线程的栈 。
go1.10\src\runtime\proc.go
G没办法自己运行,必须通过M运行
M通过通过调度,执行G
从M挂载P的runq中找到G,执行G
golang sync.pool对象复用 并发原理 缓存池 在go http每一次go serve(l)都会构建Request数据结构 。在大量数据请求或高并发的场景中,频繁创建销毁对象,会导致GC压力 。解决办法之一就是使用对象复用技术 。在http协议层之下,使用对象复用技术创建Request数据结构 。在http协议层之上,可以使用对象复用技术创建(w,*r,ctx)数据结构 。这样即可以回快TCP层读包之后的解析速度,也可也加快请求处理的速度 。
先上一个测试:
结论是这样的:
貌似使用池化,性能弱爆了???这似乎与net/http使用sync.pool池化Request来优化性能的选择相违背 。这同时也说明了一个问题 , 好的东西,如果滥用反而造成了性能成倍的下降 。在看过pool原理之后,结合实例,将给出正确的使用方法,并给出预期的效果 。
sync.Pool是一个 协程安全 的 临时对象池。数据结构如下:
local 成员的真实类型是一个 poolLocal 数组,localSize 是数组长度 。这涉及到Pool实现,pool为每个P分配了一个对象 , P数量设置为runtime.GOMAXPROCS(0) 。在并发读写时,goroutine绑定的P有对象,先用自己的,没有去偷其它P的 。go语言将数据分散在了各个真正运行的P中 , 降低了锁竞争 , 提高了并发能力 。
不要习惯性地误认为New是一个关键字,这里的New是Pool的一个字段,也是一个闭包名称 。其API:
如果不指定New字段,对象池为空时会返回nil , 而不是一个新构建的对象 。Get()到的对象是随机的 。
原生sync.Pool的问题是 , Pool中的对象会被GC清理掉,这使得sync.Pool只适合做简单地对象池,不适合作连接池 。
pool创建时不能指定大?。挥惺肯拗?。pool中对象会被GC清掉,只存在于两次GC之间 。实现是pool的init方法注册了一个poolCleanup()函数,这个方法在GC之前执行,清空pool中的所有缓存对象 。
为使多协程使用同一个POOL 。最基本的想法就是每个协程,加锁去操作共享的POOL,这显然是低效的 。而进一步改进,类似于ConcurrentHashMap(JDK7)的分Segment,提高其并发性可以一定程度性缓解 。
注意到pool中的对象是无差异性的 , 加锁或者分段加锁都不是较好的做法 。go的做法是为每一个绑定协程的P都分配一个子池 。每个子池又分为私有池和共享列表 。共享列表是分别存放在各个P之上的共享区域,而不是各个P共享的一块内存 。协程拿自己P里的子池对象不需要加锁,拿共享列表中的就需要加锁了 。
Get对象过程:
Put过程:
如何解决Get最坏情况遍历所有P才获取得对象呢:
方法1止前sync.pool并没有这样的设置 。方法2由于goroutine被分配到哪个P由调度器调度不可控,无法确保其平衡 。
由于不可控的GC导致生命周期过短,且池大小不可控,因而不适合作连接池 。仅适用于增加对象重用机率 , 减少GC负担 。2
执行结果:
单线程情况下,遍历其它无元素的P , 长时间加锁性能低下 。启用协程改善 。
结果:
测试场景在goroutines远大于GOMAXPROCS情况下,与非池化性能差异巨大 。
测试结果
可以看到同样使用*sync.pool,较大池大小的命中率较高 , 性能远高于空池 。
结论:pool在一定的使用条件下提高并发性能,条件1是协程数远大于GOMAXPROCS,条件2是池中对象远大于GOMAXPROCS 。归结成一个原因就是使对象在各个P中均匀分布 。
池pool和缓存cache的区别 。池的意思是,池内对象是可以互换的,不关心具体值,甚至不需要区分是新建的还是从池中拿出的 。缓存指的是KV映射,缓存里的值互不相同 , 清除机制更为复杂 。缓存清除算法如LRU、LIRS缓存算法 。
池空间回收的几种方式 。一些是GC前回收,一些是基于时钟或弱引用回收 。最终确定在GC时回收Pool内对象 , 即不回避GC 。用java的GC解释弱引用 。GC的四种引用:强引用、弱引用、软引用、虚引用 。虚引用即没有引用,弱引用GC但有空间则保留,软引用GC即清除 。ThreadLocal的值为弱引用的例子 。
regexp 包为了保证并发时使用同一个正则,而维护了一组状态机 。
fmt包做字串拼接 , 从sync.pool拿[]byte对象 。避免频繁构建再GC效率高很多 。
关于go语言处理并发请求和go语言并行的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站 。
推荐阅读
- gis如何添加dwg的文件,arcgis怎么加载dwg格式
- 电视电脑插座怎么接线,电视电脑插座是干嘛用的
- 电脑开机前提是什么,开机前应注意哪些事项
- gis人流密度,人流密度分析图
- 怎么吧dxf转为shp用gis,将dxf转为shp
- 星舰模拟飞行游戏,星舰模拟器
- python紫色函数 python中紫色字体代表
- 黑客常用linux命令,黑客用的linux
- linuxvps搭建网站,linux搭建web站点