Go:并发和调度程序亲和性

g-01.png

将一个goroutine从一个OS线程切换到另一个线程是有成本的,并且如果这种情况发生得太频繁,可能会使应用程序变慢。但是,随着时间的流逝,Go调度程序已经解决了这个问题。现在,当并发工作时,它可以在goroutine和线程之间提供关联。让我们回溯几年前来了解这种改进。

原始问题

在Go的早期,比如Go 1.0和1.1,当使用更多OS线程(即,更高的GOMAXPROCS值)运行并发代码时,该语言将面临性能下降的问题。让我们从计算素数的文档中使用一个示例开始:

g-02.png

这是使用多个GOMAXPROCS值计算前十万个素数时Go 1.0.3的基准:

name time/op  
Sieve 19.2s ± 0%  
Sieve-2 19.3s ± 0%  
Sieve-4 20.4s ± 0%  
Sieve-8 20.4s ± 0%

要了解这些结果,我们需要了解此时如何设计调度程序。在Go的第一个版本中,调度程序只有一个全局队列,所有线程都可以在其中推送并获取goroutine。这是一个应用程序的示例,该应用程序最多将两个操作系统线程(以下架构中的M)运行(通过将GOMAXPROCS设置为两个来定义):

g-03.png

仅具有一个队列并不能保证goroutine将在同一线程上恢复。准备就绪的第一个线程将提取一个等待的goroutine并将其运行。因此,它涉及将goroutines从一个线程转移到另一个线程,并且在性能方面代价很高。这是一个带有阻塞通道的示例:

  • Goroutine#7在通道上阻塞,正在等待消息。收到消息后,goroutine将推入全局队列:

g-04.png

  • 然后,通道推送消息,并且goroutine #X将在可用线程上运行,而goroutine#8在通道上阻塞:

g-05.png

  • goroutine#7现在在可用线程上运行:

g-06.png

现在,goroutine在不同的线程上运行。具有单个全局队列也将迫使调度程序具有一个覆盖所有goroutines调度操作的单个全局互斥量。这是使用pprof创建的CPU配置文件,其中GOMAXPROCS设置为height:

Total: 8679 samples  
3700 42.6% 42.6% 3700 42.6% runtime.procyield  
1055 12.2% 54.8% 1055 12.2% runtime.xchg  
753 8.7% 63.5% 1590 18.3% runtime.chanrecv  
677 7.8% 71.3% 677 7.8% dequeue  
438 5.0% 76.3% 438 5.0% runtime.futex  
367 4.2% 80.5% 5924 68.3% main.filter  
234 2.7% 83.2% 5005 57.7% runtime.lock  
230 2.7% 85.9% 3933 45.3% runtime.chansend  
214 2.5% 88.4% 214 2.5% runtime.osyield  
150 1.7% 90.1% 150 1.7% runtime.cas

procyieldxchgfutexlock都与Go调度程序的全局互斥量有关。我们清楚地看到,应用程序将大部分时间都花在了锁定上。

这些问题不允许Go发挥处理器的优势,并且在Go 1.1中使用新的调度程序解决了这些问题。

并发中的亲和性

Go 1.1附带了新调度程序的实现和本地goroutine队列的创建。如果存在本地goroutine,此改进避免了锁定整个调度程序,并允许它们在同一OS线程上工作。

由于线程可以阻塞系统调用,并且不受限制的线程数没有限制,因此Go引入了处理器的概念。处理器P代表一个正在运行的OS线程,它将管理本地goroutine队列。这是新的架构:

g-08.png

这是Go 1.1.2中新计划程序的新基准:

name time/op  
Sieve 18.7s ± 0%  
Sieve-2 8.26s ± 0%  
Sieve-4 3.30s ± 0%  
Sieve-8 2.64s ± 0%

Go现在真正利用了所有可用的CPU。 CPU配置文件也已更改:

Total: 630 samples  
163 25.9% 25.9% 163 25.9% runtime.xchg  
113 17.9% 43.8% 610 96.8% main.filter  
93 14.8% 58.6% 265 42.1% runtime.chanrecv  
87 13.8% 72.4% 206 32.7% runtime.chansend  
72 11.4% 83.8% 72 11.4% dequeue  
19 3.0% 86.8% 19 3.0% runtime.memcopy64  
17 2.7% 89.5% 225 35.7% runtime.chansend1  
16 2.5% 92.1% 280 44.4% runtime.chanrecv2  
12 1.9% 94.0% 141 22.4% runtime.lock  
9 1.4% 95.4% 98 15.6% runqput

与锁定相关的大多数操作已被删除,标记为chanXXXX的操作仅与通道相关。但是,如果调度程序改善了goroutine和线程之间的亲和力,则在某些情况下可以减少这种亲和力。

亲和性限制

要了解亲和性的限制,我们必须了解对本地和全局队列的处理。本地队列将用于所有需要系统调用的操作,例如阻塞通道和选择的操作,等待计时器和锁定。但是,两个功能可能会限制goroutine和线程之间的关联:

  • Worker抢夺。当处理器 P 的本地队列中没有足够的worker时,如果全局队列和网络轮询器为空,它将从其他 P 窃取goroutine。当被抢夺时,goroutine将在另一个线程上运行。
  • 系统调用。当发生系统调用时(例如文件操作,http调用,数据库操作等),Go会将运行中的OS线程移入阻塞模式,让新线程处理当前P上的本地队列。

但是,通过更好地管理本地队列的优先级,可以避免这两个限制。 Go 1.5旨在为goroutine在通道上来回通信提供更高的优先级,从而优化与分配的线程的亲和力。

为了增强亲和力

如前所述,在通道上来回通信的goroutine会导致频繁的阻塞,即在本地队列中频繁地重新排队。但是,由于本地队列具有FIFO实现,因此,如果另一个goroutine正在占用线程,则unblock goroutine不能保证尽快运行。这是一个goroutine的示例,该例程现在可以运行并且以前在通道上被阻止:

g-07.png

Goroutine#9在通道上被阻塞后恢复。但是,它必须在运行之前等待#2,#5和#4。在此示例中,goroutine#5将占用其线程,从而延迟goroutine#9,并使之处于被其他处理器窃取的危险中。从Go 1.5开始,由于其 P 的特殊属性,从阻塞通道返回的goroutine现在将优先运行:

g-10.png

Goroutine#9现在被标记为下一个可运行的。这种新的优先级划分功能使goroutine可以在再次被阻塞之前迅速运行。然后,其他goroutine将具有运行时间。此更改对Go标准库改善了某些软件包的性能产生了总体积极影响。

Image placeholder
TAO12332166
未设置
  27人点赞

没有讨论,发表一下自己的看法吧

推荐文章
Ambassador 0.52 新特性:会话亲和性、负载均衡控制、gRPC-Web

本文由公众号EAWorld翻译发表,转载需注明出处。作者:RichardLi 译者:白小白 原文:http://t.cn/E6cZoyG现时的云原生应用由多种异构的服务或者微服务组成,服务间、服务与客

【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!

Java并发基础常见面试题总结 1.什么是线程和进程? 1.1.何为进程? 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

《Go语言程序设计》读书笔记(六) 基于共享变量的并发

竞争条件 在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。在有两个或更多goroutine的程序中,每一个goroutine内的语句也是按照既定的顺序去执

Peloton:优步开源的统一资源调度器

Peloton最初是在2018年11月份引入的,并在2019年3月份正式开源。Peloton是为像优步这样拥有数百万个容器和数万个节点的规模公司设计的,它提供了高级的资源管理特性,比如弹性资源共享、层

PHP 进程池与轮询调度算法实现多任务

phper请了解进程调度策略,CPU时间片,进程控制【创建,销毁,回收,进程信号】与及进程运行流程和基本的进程组,信号中断原理,以及进程之间的关系。关于进程的更多内容可参考本人前面撸过的文章或是百度了

Hadoop YARN:调度性能优化实践

背景YARN作为Hadoop的资源管理系统,负责Hadoop集群上计算资源的管理和作业调度。美团的YARN以社区2.7.1版本为基础构建分支。目前在YARN上支撑离线业务、实时业务以及机器学习业务。离

一个简单的基于 Redis 的分布式任务调度器 —— Java 语言实现

折腾了一周的JavaQuartz集群任务调度,很遗憾没能搞定,网上的相关文章也少得可怜,在多节点(多进程)环境下Quartz似乎无法动态增减任务,恼火。无奈之下自己撸了一个简单的任务调度器,结果只花了

Oracle调度作业引起的空间骤增问题处理记录

1、 问题描述接到客户电话告知,说是近期数据库磁盘目录空间增长特别快, 1-2 天就满了。联系客户对数据库进行分析后,发现造成空间急剧增长的原因主要是 job 任务的 trace 文件产生的量太大造成

宜信微服务任务调度平台建设实践|分享实录

导读:如今,无论是互联网应用还是企业级应用,都充斥着大量的批处理任务,常常需要一些任务调度系统帮助我们解决问题。随着微服务化架构的逐步演进,单体架构逐渐演变为分布式、微服务架构。内容来源:宜信技术学院

宜信开源|微服务任务调度平台SIA-TASK入手实践

引言最近宜信开源微服务任务调度平台SIA-TASK,SIA-TASK属于分布式的任务调度平台,使用起来简单方便,非常容易入手,部署搭建好SIA-TASK任务调度平台之后,编写TASK后配置JOB进行调

Go语言高级编程_1.5 面向并发的内存模型

1.5面向并发的内存模型 在早期,CPU都是以单核的形式顺序执行机器指令。Go语言的祖先C语言正是这种顺序编程语言的代表。顺序编程语言中的顺序是指:所有的指令都是以串行的方式执行,在相同的时刻有且仅有

Go语言高级编程_1.6 常见的并发模式

1.6常见的并发模式 Go语言最吸引人的地方是它内建的并发支持。Go语言并发体系的理论是C.A.RHoare在1978年提出的CSP(CommunicatingSequentialProcess,通讯

Go语言高级编程_2.1.1 最简CGO程序

2.1快速入门 本节我们将通过一系列由浅入深的小例子来快速掌握CGO的基本用法。 2.1.1最简CGO程序 真实的CGO程序一般都比较复杂。不过我们可以由浅入深,一个最简的CGO程序该是什么样的呢?要

02.7. 并发

interface Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计所折服。 什么是interface 简单

Java 8 并发教程:线程和执行器

Thread和Runnable 所有的现代操作系统都通过进程和线程来支持并发。进程是通常彼此独立运行的程序的实例,比如,如果你启动了一个Java程序,操作系统产生一个新的进程,与其他程序一起并行执行。

Java 8 并发教程:同步和锁

出于简单的因素,这个教程的代码示例使用了定义在这里的两个辅助函数sleep(seconds)和stop(executor)。 同步 在上一章中,我们学到了如何通过执行器服务同时执行代码。当我们编写这种

Java 8 并发教程:原子变量和 ConcurrentMap

出于简单的因素,这个教程的代码示例使用了定义在这里的两个辅助函数sleep(seconds)和stop(executor)。 AtomicInteger java.concurrent.atomic包

高并发业务场景下的秒杀解决方案 (初探)

文章简介 本文内容是对并发业务场景出现超卖情况而写的一片解决方案。主要是利用到了Redis中的队列技术。 超卖介绍 所谓的超卖,就是我们的售卖量大于了物品的库存量。该情况一般出现在电商系统中促销类的业

高并发设计笔记

基础篇 高并发系统:它的通用设计方法是什么? 高并发系统设计的三种通用方法:Scale-out、缓存和异步。 这三种方法可以在做方案设计时灵活地运用,但它不是具体实施的方案,而是三种思想,在实际运用中

高并发设计笔记

基础篇 高并发系统:它的通用设计方法是什么? 高并发系统设计的三种通用方法:Scale-out、缓存和异步。 这三种方法可以在做方案设计时灵活地运用,但它不是具体实施的方案,而是三种思想,在实际运用中

Redis为什么是单线程、及高并发快的3大原因详解

Redis的高并发和快速原因 1.redis是基于内存的,内存的读写速度非常快; 2.redis是单线程的,省去了很多上下文切换线程的时间; 3.redis使用多路复用技术,可以处理并发的连接。非阻塞

并发测试

并发 这是我们的计划:同事已经写了一个CheckWebsites的函数检查URL列表的状态。 packageconcurrency typeWebsiteCheckerfunc(string)boo

高并发设计笔记(续篇)

感谢大家对上篇的阅读和点赞,由于内容比较多,选择分开记录。 如果想深入了解,还是建议去购买学习该门课程《高并发系统设计》 分布式服务篇系统架构:每秒1万次请求的系统要做服务化拆分吗?了解实际业务中会

高并发下的接口幂等性解决方案!

一、背景我们实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返回一样的结果。例如:前端重复提交选中的数据,应该后台只产生对应这个数据的一个反应结果。我们发起一笔付款请求,应该只扣用户账户一

96秒100亿!如何抗住双11高并发流量?

今年双11全民购物狂欢节进入第十一个年头,1分36秒,交易额冲到100 亿 !比2018年快了近30 秒,比2017年快了近1分半!这个速度再次刷新天猫双11成交总额破100亿的纪录。那么如何抗住双1