Java与Go的并发世界:理解Work Sharing与Work Stealing

概述

最近在理解Golang中的Per P概念,于是我就去Go的源码中挖呀挖,结果挖到了Go的调度器设计。

Scalable Go Scheduler Design Doc

Golang的调度器设计文档提到了Go中的P(OS线程)调度器使用的是work-stealing调度算法论文。

Scheduling Multithreaded Computationsby Work Stealing

论文中提到了两个多线程调度算法:work sharing和work stealing。

scheduling multithreaded computations

什么是work sharing?
什么是work stealing?

Work Sharing(工作共享)

先来个专业解释:

在工作共享策略中,当一个处理器生成新的线程时,调度器会尝试将部分线程迁移到其他处理器上,以期分派工作到未充分利用的处理器。这样,工作可以在处理器之间共享,从而实现并行计算。

有点抽象,我个人理解是这样:work sharing:工作共享,是多核多线程并行处理任务的调度算法。 它的基本思想是把任务均匀的分摊到任务执行节点(这里的计算节点就是OS线程)。

Work sharing算法每个任务执行节点使用共享的任务队列,也叫全局队列,调度器负责分配任务给这些任务执行节点。

应用层

我们身处应用层的小开发怎么来理解。我觉得对于Java程序员来说,work sharing就是线程池(ThreadPoolExecturor)。Java的ThreadPoolExecturor有一个共享的任务队列,有一个或者多个任务线程。所有的任务线程都从共享的任务队列中获取任务并执行。

Java ThreadPoolExecutor diagram

优劣

  1. 优点
    因为所有的任务都由调度器统一分配,所以可以保证所有的处理器都保持负载均衡,避免了某些处理器空闲而其他处理器繁忙的情况。

  2. 缺点
    Work Sharing 要求处理器间经常进行任务交换,这可能会带来一些额外的开销。例如上下文切换的成本和处理器间的通信成本等。同时,这样的设计也使得调度器成为了一个明显的瓶颈。

Work Stealing(工作窃取)

在工作窃取调度器中,计算机系统中的每个处理器都有一个需要执行的工作项(计算任务,线程)队列。每个工作项由一系列顺序执行的指令组成,但是在执行过程中,工作项也可能生成新的工作项,这些新的工作项可与其它工作并行执行。这些新产生的工作项最初会被放在执行当前工作项的处理器队列中。当一个处理器的工作执行完毕,它会查看其他处理器的队列并“窃取”他们的工作项。实际上,工作窃取将调度工作分摊到空闲的处理器上,并且只要所有处理器都有工作要做,就不会产生调度开销。

我们身处应用层的小开发怎么来理解。其实就是每个线程有一个自己的任务队列(这样任务队列就去中心化了),每个线程先从自己的线程本地来获取任务执行,但是当自己的任务队列空了就从其他线程的任务队列中“偷”一些任务继续执行。

work stealing diagram

work sharing vs work stealing

我们来对比一下work sharing和work stealing:

Work SharingWork Stealing
线程分配方式当一个处理器产生新线程时,调度器将线程迁移至其他处理器闲置处理器主动从忙碌处理器窃取线程执行
适用情况所有处理器均有工作任务时,能保持负载均衡一部分处理器空闲,一部分处理器忙碌时,能主动窃取任务,保持负载均衡
线程迁移频率总是会发生线程迁移,可能会影响性能在所有处理器都有任务执行的情况下不迁移线程,减少了线程调度开销
主动/被动被动分配 – 调度器主动将任务分配给处理器主动窃取 – 处理器主动从其他处理器窃取任务
实现复杂性实现起来相对简单,易于理解实现起来相对复杂,需要处理窃取行为等细节问题
典型应用实例Java中的ThreadPoolExecutorJava中的ForkJoinPool

ForkJoinPool解析

在Java中,ForkJoinPool实现了Work stealing,我们来看下ForkJoinPool中work stealing相关的组件:

  1. 工作线程
  2. 任务队列
  3. 调度器
  4. 任务
  5. 工作窃取策略
  6. 同步机制

工作线程

ForkJoinPool中Work Thread类为:ForkJoinWorkerThread,我们来看下构造函数:


// 根据指定的线程组和ForkJoinPool创建新的ForkJoinWorkerThread
// 确定是否使用系统类加载器和是否清除线程本地变量
super(group, null, pool.nextWorkerThreadName(), 0L, !clearThreadLocals);// 保存ForkJoinPool,并且获取并保存uncaughtExceptionHandler
UncaughtExceptionHandler handler = (this.pool = pool).ueh;// 为当前工作线程创建新的ForkJoinPool.WorkQueue
this.workQueue = new ForkJoinPool.WorkQueue(this, 0);// 如果需要清除线程本地变量,则设置清除标志
if (clearThreadLocals)workQueue.setClearThreadLocals();// 设置线程为守护线程
super.setDaemon(true);// 如果有uncaughtExceptionHandler,则设置给当前线程
if (handler != null)super.setUncaughtExceptionHandler(handler);// 如果需要使用系统类加载器,则设置系统类加载器为当前线程的上下文类加载器
if (useSystemClassLoader)super.setContextClassLoader(ClassLoader.getSystemClassLoader());

COPY

以上代码中,我们看到工作线程有一个workQueue。

任务队列

任务队列ForkJoinPool是双端队列:从队列头部获取任务,从队列尾部窃取任务。

我们只关注构造函数:

工作队列关联了一个ForkJoin工作线程,并且任务队列的内存存储使用了环形缓冲区(Ring Buffer)来存储任务。

/*** 构造函数。对于拥有队列,大多数字段是在pool.registerWorker中的线程启动时初始化的。** @param owner 所有者,此工作队列属于哪个 ForkJoinWorkerThread* @param config 配置参数,可能包括了如何处理该工作队列的各种配置*/WorkQueue(ForkJoinWorkerThread owner, int config) {// 设置此工作队列的所有者为指定的 ForkJoinWorkerThreadthis.owner = owner;// 设置此工作队列的配置为指定的配置this.config = config;// 初始化 base 和 top 表示队列的头和尾部索引为 1base = top = 1;}

COPY

调度器

ForkJoinPool的调度器主要是负责任务提交,任务队列管理,工作线程和工作窃取的处理。

在了解ForkJoinPool任务提交之前我们得先了解一下任务队列, 在ForkJoinPool中任务队列是一个数组存放了所有工作线程的任务队列,在提交任务时会随机从这个任务队列数组中选取一个然后把任务提交到这个任务队列中:


WorkQueue[] queues;                  // main registry

COPY

如下图:

file

提交任务

提交任务函数:

private <T> ForkJoinTask<T> poolSubmit(boolean signalIfEmpty,ForkJoinTask<T> task) 

COPY

提交任务流程:

提交任务

任务窃取

任务窃取是ForkJoinPool的核心了吧,窃取主要涉及两个核心方法:scanawaitWork


while ((src = scan(w, src, r)) >= 0 || (src = awaitWork(w)) == 0);

COPY

scan

scan流程:

  1. 首先从自己的任务队列中获取一个任务并执行
  2. 如果自己的任务队列中就尝试从其他任务队列队尾窃取任务

这个函数代码有点麻烦,有点费脑子,大家自己看吧。

总结

Work stealing可以提升并发和并行,因为空闲的线程会主动窃取其他线程中任务队列的任务从而提升并行处理能力。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电力设备热设计原理(二)

本篇为西安交通大学本科课程《电力设备设计原理》的笔记。 本篇为这一单元的第二篇笔记。上一篇传送门。 电力设备传导换热 主要讨论稳态导热的计算。 通过单层和多层平壁的传导 如上图所示的大平板是一维传导问题&#xff0c;流过平板的热流量和平板两侧温度和平板厚度之间…

c++的学习之路:6、类和对象(2)

一、 构造函数 如果一个类什么成员都没有&#xff0c;那么他是一个空类吗&#xff1f;在c的创建时&#xff0c;就规定了在类没有成员时&#xff0c;也会有六个默认的成员&#xff0c;简称6个默认成员函数&#xff0c;如下图所示 先介绍一下构造函数&#xff0c;这里就利用代码…

第四百三十六回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"不同平台上换行的问题"相关的内容&#xff0c;本章回中将介绍如何在页面上显示蒙板层.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我…

鸿蒙OS开发实例:【ArkTS类库多线程CPU密集型任务TaskPool】

CPU密集型任务是指需要占用系统资源处理大量计算能力的任务&#xff0c;需要长时间运行&#xff0c;这段时间会阻塞线程其它事件的处理&#xff0c;不适宜放在主线程进行。例如图像处理、视频编码、数据分析等。 基于多线程并发机制处理CPU密集型任务可以提高CPU利用率&#x…

AMD GPUs - Radeon™ PRO W7900与NVIDIA 4000系列GPU性能

文心一言 RTX 4090的性能高于AMD Radeon PRO W7900。 RTX 4090具有760亿个晶体管、16384个CUDA核心和24GB高速镁光GDDR6X显存&#xff0c;在4K分辨率的游戏中持续以超过100FPS运行。RTX 4090采用全新的DLSS 3技术&#xff0c;相比3090TI&#xff0c;性能提升可达2~4倍&#x…

STM32F103 CubeMX 使用USB生成键盘设备

STM32F103 CubeMX 使用USB生成键盘设备 基础信息HID8个数组各自的功能 生成代码代码编写添加申明信息main 函数编写HID 修改1. 修改报文描述符2 修改 "usbd_hid.h" 中的申明文件 基础信息 软件版本&#xff1a; stm32cubmx&#xff1a;6.2 keil 5 硬件&#xff1a;…

超分辨率(4)--基于A2N实现图像超分辨率重建

一.项目介绍 已有研究表明&#xff0c;注意力机制对高性能超分辨率模型非常重要。然而&#xff0c;很少有工作真正讨论“为什么注意力会起作用&#xff0c;它又是如何起作用的”。 文章中尝试量化并可视化静态注意力机制并表明&#xff1a;并非所有注意力模块均有益。提出了…

vue3+threejs新手从零开发卡牌游戏(二十):添加卡牌被破坏进入墓地逻辑

在game目录下新建graveyard文件夹存放墓地相关代码&#xff1a; game/graveyard/p1.vue&#xff0c;这里主要设置了墓地group的位置&#xff1a; <template><div></div> </template><script setup lang"ts"> import { reactive, ref,…

Python入门(八)

引入 引入函数 为了减少代码的冗余&#xff0c;减轻我们的工作量&#xff0c;我们常常将代码分块编写&#xff0c;在Python中更是如此&#xff0c;那么我们怎么在一个新的程序文件中调用我们已经编写好程序文件的函数&#xff0c;我们使用import。我们先写一个first.py为例语…

WinForm_初识_事件_消息提示

文章目录 WinForm开发环境的使用软件部署的架构B/S 架构应用程序C/S 架构应用程序 创建 Windows 应用程序窗口介绍查看设计窗体 Form1.cs 后台代码窗体 Form1.cs窗体的常用属性 事件驱动机制事件的应用事件的测试测试事件的级联响应常用控件的事件事件响应的公共方法 消息提示的…

CCIE-02-PPPoE

目录 实验条件网络拓朴实验目标 开始配置R1验证效果 实验条件 网络拓朴 实验目标 R2为PPPoE Server&#xff0c;已预配了相关信息&#xff1b;R1作为PPPoE Client&#xff0c;进行PPPoE拨号 用户名为R1&#xff0c;密码为cisco &#xff0c; 采用CHAP的认证方式&#xff0c;I…

算法学习——LeetCode力扣补充篇3(143. 重排链表、141. 环形链表、205. 同构字符串、1002. 查找共用字符、925. 长按键入)

算法学习——LeetCode力扣补充篇3 143. 重排链表 143. 重排链表 - 力扣&#xff08;LeetCode&#xff09; 描述 给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → …

Python学习笔记 - 如何在google Colab中显示图像?

这里是使用的opencv进行图片的读取&#xff0c;自然也是想使用opencv的imshow方法来显示图像&#xff0c;但是在google Colab中不可以使用&#xff0c;所以寻找了一下变通的显示方法。 方法1&#xff1a;使用matplotlib 使用plt需要交换一下r、b通道&#xff0c;否则显示不正常…

第十九章 UML

统一建模语言(Unified Modeling Language&#xff0c; UML )是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言&#xff0c;是非专利的第三代建模和规约语言。 UML 是面向对象设计的建模工具&#xff0c;独立于任何具体程序设计语言。 一、简介 UML 作为一…

正大国际:做qi货靠运气多点还是靠自己学习到认知度?

一个人能赚到自己认知范围以外的钱靠的是运气&#xff0c;能赚到自己认知范围内的钱靠的是能力。期货市场试错成本较高&#xff0c;交易自己熟悉和擅长的领域会大大降低失败概率。期货市场机会很多&#xff0c;陷阱也很多&#xff0c;如何坚持做认知范围内的决策是一个重要的交…

docker部署开源软件的国内镜像站点

下载镜像 docker pull registry.cn-beijing.aliyuncs.com/wuxingge123/le_monitor:latestdocker-compose部署 vim docker-compose.yml version: 3 services:le_monitor:container_name: le_monitorimage: registry.cn-beijing.aliyuncs.com/wuxingge123/le_monitor:latestpo…

算法——距离计算

距离计算常用的算法包括欧氏距离、曼哈顿距离、切比雪夫距离、闵可夫斯基距离、余弦相似度等。这些算法在数据挖掘、机器学习和模式识别等领域中被广泛应用。 1.欧氏距离 欧式距离也称欧几里得距离&#xff0c;是最常见的距离度量&#xff0c;衡量的是多维空间中两个点之间的…

心里健康(健康与生存)

你还认为 健康 是有个强壮的身体吗&#xff1f; 这样 肯定是错的 我们来说说 什么是健康与现代健康观 以及影响健康的因素 有哪些&#xff1f; 以及 健康对个人与社会的意义 首先 我们来看看 健康演变过程 公元 1000 年 Health 首次出现 它代表了 强壮 健全 完整等含义 健康 …

十四.PyEcharts基础学习

目录 1-PyEcharts介绍 优点&#xff1a; 安装: 官方文档&#xff1a; 2-PyEcharts快速入门 2.1 第一个图表绘制 2.2 链式调用 2.3 opeions配置项 2.4 渲染图片文件 2.5 使用主题 3-PyEcharts配置项 3.1 初始化配置项InitOpts InitOpts 3.2 全局配置项set_global_o…

京东云明修“价格战”,暗渡“政企云”

文&#xff5c;白 鸽 编&#xff5c;王一粟 云计算行业越来越“卷”&#xff0c;一边卷大模型&#xff0c;一边卷价格。 2024 刚一开年&#xff0c;阿里云就宣布百余款产品大降价&#xff0c;最高降幅达55%。在阿里云宣布降价后&#xff0c;京东云紧随其后宣布&#xff0…