首页>>后端>>Golang->golang高并发项目(golang高并发解决方案)

golang高并发项目(golang高并发解决方案)

时间:2023-12-05 本站 点击:0

golang csp 模型

调度器 由三方面实体构成:

三者对应关系:

上图有2个 物理线程 M,每一个 M 都拥有一个上下文(P),每一个也都有一个正在运行的goroutine(G)。

P 的数量可由 runtime.GOMAXPROCS() 进行设置,它代表了真正的并发能力,即可有多少个 goroutine 同时运行。

调度器为什么要维护多个上下文P 呢? 因为当一个物理线程 M 被阻塞时,P 可以转而投奔另一个OS线程 M (即 P 带着 G 连茎拔起,去另一个 M 节点下运行)。这是 Golang调度器厉害的地方,也是高并发能力的保障。

为什么我全力推荐Golang

讨论哪个语言更好,就像在争论姚明和刘翔谁是更优秀的运动员。因为各自的坐标象限不同,常常会陷入一个难有结论怪圈。

所以本文绝不是在说Golang是比其他语言更好的语言。Golang只是最值得推荐的语言,尤其适合快速成长中的后端研发团队。

我推荐Golang的主要理由,并不是技术性的要素:不是他的高并发能力,编译的速度,跨平台能力,内存效率,也不是社区的活跃度等等。

事实上,创业之后,或者说成为一个技术管理者之后,技术优点就已经不再是我推荐任何一种语言的关键因素了。

因为,对于一个研发团队来说,项目成败的关键因素是:成本、质量和时间!

1、人力资源的成本

人力资源是研发团队最重要的资源,也是唯一的资源。其成本不仅仅是团队要支付的薪资代价。也包括获得资源的难易程度,例如招聘和培训的速度。以及维持资源,也就是保持员工满意度或者说士气的代价,也就是管理成本。(上述成本不仅指钱,时间也是非常昂贵的成本)

Golang有一系列特点,使它既容易上手,又易于维护。Golang可以让初阶和中阶工程师,经过少许培训,就写出相当不错的代码。直接点说,一票1-2年经验少许灵性的年轻工程师转Golang,只要少许指导,很快就可以写出高并发高负载能力生产级别的代码,而且质量相当有保证。而同样的工程,如果用C++或java等语言,则需要至少3-5年经验的工程师来完成,同时质量还是要让人担心。

那么,对于团队特别是成长型的或创业团队来说,现在有Golang这样一种语言,可以让大量初阶和中级工程师承担主要开发工作,还能保证相当优秀的结果,从资金成本和时间成本控制的角度,简直就是美梦成真。

2、项目研发的效率

说到高并发高负载,让我不能不想起nginx。nginx在2004年从web server领域横空出世,所向披靡。精巧严谨易于维护和扩展的代码结构,也是教科书级别的。

但是要知道,一个用C写出一个nginx,是需要世界上最优秀的工程师的。这样的工程师,不仅团队里面没有,连遇到一个都很难。

可现在,我再告诉你,一个使用Golang的中级工程师,就已经可以写出性能与nginx相近的高并发高负载应用。而且不仅性能相近,而且需要的代码行数和开发时间也短很多。这对于团队成员来说,这很可能是决定生死存亡还是走上人生巅峰的区别。

--

总之:

对于团队管理者来说,Golang可以让团队用更低的人力成本,更快的速度,更高的质量,完成项目研发。

对于工程师来说,Golang可以让人有更多的时间去思考和生活。

所以,我推荐Golang。

【golang】高并发下TCP常见问题解决方案

首先,看一下TCP握手简单描绘过程:

其握手过程原理,就不必说了,有很多详细文章进行叙述,本文只关注研究重点。

在第三次握手过程中,如果服务器收到ACK,就会与客户端建立连接,此时内核会把连接从半连接队列移除,然后创建新的连接,并将其添加到全连接队列,等待进程调用。

如果服务器繁忙,来不及调用连接导致全连接队列溢出,服务器就会放弃当前握手连接,发送RST给客户端,即connection reset by peer。

在linux平台上,客户端在进行高并发TCP连接处理时,最高并发数量都要受系统对用户单一进程同时打开文件数量的限制(这是因为系统每个TCP都是SOCKET句柄,每个soker句柄都是一个文件),当打开连接超过限制,就会出现too many open files。

使用下指令查看最大句柄数量:

增加句柄解决方案

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效率高很多。

Golang入门到项目实战 | golang并发变成之通道channel

Go提供了一种称为通道的机制,用于在goroutine之间共享数据。当您作为goroutine执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换。

根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行goroutine之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间两个goroutine之间的交换。缓冲通道没有这样的保证。

通道由make函数创建,该函数指定chan关键字和通道的元素类型。

这是创建无缓冲和缓冲通道的代码块:

语法

使用内置函数make创建无缓冲和缓冲通道。make的第一个参数需要关键字chan,然后是通道允许交换的数据类型。

这是将值发送到通道的代码块需要使用-运算符:

语法

一个包含5个值的缓冲区的字符串类型的goroutine1通道。然后我们通过通道发送字符串“Australia”。

这是从通道接收值的代码块:

语法

- 运算符附加到通道变量(goroutine1)的左侧,以接收来自通道的值。

在无缓冲通道中,在接收到任何值之前没有能力保存它。在这种类型的通道中,发送和接收goroutine在任何发送或接收操作完成之前的同一时刻都准备就绪。如果两个goroutine没有在同一时刻准备好,则通道会让执行其各自发送或接收操作的goroutine首先等待。同步是通道上发送和接收之间交互的基础。没有另一个就不可能发生。

在缓冲通道中,有能力在接收到一个或多个值之前保存它们。在这种类型的通道中,不要强制goroutine在同一时刻准备好执行发送和接收。当发送和接收阻塞时也有不同的条件。只有当通道中没有要接收的值时,接收才会阻塞。仅当没有可用缓冲区来放置正在发送的值时,发送才会阻塞。

实例

运行结果


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Golang/12553.html