4.1-真实世界的并发

2023-11-10

复习

  • 并发编程的基本工具:线程库、互斥和同步

本次课回答的问题

  • Q: 什么样的任务是需要并行/并发的?它们应该如何实现?

本次课主要内容

  • 高性能计算中的并发编程
  • 数据中心里的并发编程
  • 我们身边的并发编程

一、高性能计算中的并发编程

高性能计算程序:特点

“A technology that harnesses the power of supercomputers or computer clusters to solve complex problems requiring massive computation.” (IBM)

以计算为中心

  • 系统模拟:天气预报、能源、分子生物学
  • 人工智能:神经网络训练
  • 矿厂:纯粹的 hash 计算
  • HPC-China 100

高性能计算:主要挑战

计算任务如何分解

  • 计算图需要容易并行化
    • 机器-线程两级任务分解
  • 生产者-消费者解决一切
    • MPI - “a specification for the developers and users of message passing libraries”, OpenMP - “multi-platform shared-memory parallel programming in C/C++ and Fortran”
  • Parallel and Distributed Computation: Numerical Methods

线程间如何通信

  • 通信不仅发生在节点/线程之间,还发生在任何共享内存访问
  • 还记得被 mem-ordering.c 支配的恐惧吗?

例子:Mandelbrot Set

img

#include "thread.h"
#include <math.h>

int NT;
#define W 6400
#define H 6400
#define IMG_FILE "mandelbrot.ppm"

static inline int belongs(int x, int y, int t) {
  return x / (W / NT) == t;
}

int x[W][H];
int volatile done = 0;

void display(FILE *fp, int step) { 
  static int rnd = 1;
  int w = W / step, h = H / step;
  // STFW: Portable Pixel Map
  fprintf(fp, "P6\n%d %d 255\n", w, h);
  for (int j = 0; j < H; j += step) {
    for (int i = 0; i < W; i += step) {
      int n = x[i][j];
      int r = 255 * pow((n - 80) / 800.0, 3);
      int g = 255 * pow((n - 80) / 800.0, 0.7);
      int b = 255 * pow((n - 80) / 800.0, 0.5);
      fputc(r, fp); fputc(g, fp); fputc(b, fp);
    }
  }
}

void Tworker(int tid) {
  for (int i = 0; i < W; i++)
    for (int j = 0; j < H; j++)
      if (belongs(i, j, tid - 1)) {
        double a = 0, b = 0, c, d;
        while ((c = a * a) + (d = b * b) < 4 && x[i][j]++ < 880) {
          b = 2 * a * b + j * 1024.0 / H * 8e-9 - 0.645411;
          a = c - d + i * 1024.0 / W * 8e-9 + 0.356888;
        }
      }
  done++;
}

void Tdisplay() {
  float ms = 0;
  while (1) {
    FILE *fp = popen("viu -", "w"); assert(fp);
    display(fp, W / 256);
    pclose(fp);
    if (done == NT) break;
    usleep(1000000 / 5);
    ms += 1000.0 / 5;
  }
  printf("Approximate render time: %.1lfs\n", ms / 1000);

  FILE *fp = fopen(IMG_FILE, "w"); assert(fp);
  display(fp, 2);
  fclose(fp);
}

int main(int argc, char *argv[]) {
  assert(argc == 2);
  NT = atoi(argv[1]);
  for (int i = 0; i < NT; i++) {
    create(Tworker);
  }
  create(Tdisplay);
  join();
  return 0;
}

二、数据中心里的并发编程

img

Google 的数据中心

数据中心程序:特点

“A network of computing and storage resources that enable the delivery of shared applications and data.” (CISCO)

以数据 (存储) 为中心

  • 从互联网搜索 (Google)、社交网络 (Facebook/Twitter) 起家
  • 支撑各类互联网应用:微信/QQ/支付宝/游戏/网盘/……

算法/系统对 HPC 和数据中心的意义

  • 你有 1,000,000 台服务器
  • 如果一个算法/实现能快 1%,就能省 10,000 台服务器
    • 参考:对面一套房 ≈ 50 台服务器 (不计运维成本)

数据中心:主要挑战

多副本情况下的高可靠、低延迟数据访问

  • 在服务海量地理分布请求的前提下
    • 数据要保持一致 (Consistency)
    • 服务时刻保持可用 (Availability)
    • 容忍机器离线 (Partition tolerance)

这门课的问题:如何用好一台计算机?

如何用一台 (可靠的) 计算机尽可能多地服务并行的请求

  • 关键指标:QPS, tail latency, …

我们有的工具

  • 线程 (threads)
thread(start = true) {
  println("${Thread.currentThread()} has run.")
}
  • 协程 (coroutines)
    • 多个可以保存/恢复的执行流 (M2 - libco)
    • 比线程更轻量 (完全没有系统调用,也就没有操作系统状态)

数据中心:协程和线程

数据中心

  • 同一时间有数千/数万个请求到达服务器
  • 计算部分
    • 需要利用好多处理器
      • 线程 → 这就是我擅长的 (Mandelbrot Set)
      • 协程 → 一人出力,他人摸鱼
  • I/O 部分
    • 会在系统调用上 block (例如请求另一个服务或读磁盘)
      • 协程 → 一人干等,他人围观
      • 线程 → 每个线程都占用可观的操作系统资源
  • (这个问题比你想象的复杂,例如虚拟机)

Go 和 Goroutine

Go: 小孩子才做选择,多处理器并行和轻量级并发我全都要!

Goroutine: 概念上是线程,实际是线程和协程的混合体

  • 每个 CPU 上有一个 Go Worker,自由调度 goroutines
  • 执行到 blocking API 时 (例如 sleep, read)
    • Go Worker 偷偷改成 non-blocking 的版本
      • 成功 → 立即继续执行
      • 失败 → 立即 yield 到另一个需要 CPU 的 goroutine
        • 太巧妙了!CPU 和操作系统全部用到 100%

例子

// Example from "The Go Programming Language"

package main

import (
  "fmt"
  "time"
)

func main() {
  go spinner(100 * time.Millisecond)
  const n = 45
  fibN := fib(n) // slow
  fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}

func spinner(delay time.Duration) {
  for {
    for _, r := range `-\|/` {
      fmt.Printf("\r%c", r)
      time.Sleep(delay)
    }
  }
}

func fib(x int) int {
  if x < 2 { return x }
  return fib(x - 1) + fib(x - 2)
}
go run fib.go
# Fibonacci(45) = 1134903170

现代编程语言上的系统编程

Do not communicate by sharing memory; instead, share memory by communicating. ——Effective Go

共享内存 = 万恶之源

  • 在奇怪调度下发生的各种并发 bugs
    • 条件变量:broadcast 性能低,不 broadcast 容易错
    • 信号量:在管理多种资源时就没那么好用了

既然生产者-消费者能解决绝大部分问题,提供一个 API 不就好了?

package main

import "fmt"

var stream = make(chan int, 10)
const n = 4

func produce() {
  for i := 0; ; i++ {
    fmt.Println("produce", i)
    stream <- i
  }
}

func consume() {
  for {
    x := <- stream
    fmt.Println("consume", x)
  }
}

func main() {
  for i := 0; i < n; i++ {
    go produce()
  }
  consume()
}

三、我们身边的并发编程

Web 2.0 时代 (1999)

人与人之间联系更加紧密的互联网

  • “Users were encouraged to provide content, rather than just viewing it.”
  • 你甚至可以找到一些 “Web 3.0”/Metaverse 的线索

是什么成就了今天的 Web 2.0?

  • 浏览器中的并发编程:Ajax (Asynchronous JavaScript + XML)
  • HTML (DOM Tree) + CSS 代表了你能看见的一切
    • 通过 JavaScript 可以改变它
    • 通过 JavaScript 可以建立连接本地和服务器
    • 你就拥有了全世界!

人机交互程序:特点和主要挑战

特点:不太复杂

  • 既没有太多计算
    • DOM Tree 也不至于太大 (大了人也看不过来)
    • DOM Tree 怎么画浏览器全帮我们搞定了
  • 也没有太多 I/O
    • 就是一些网络请求

挑战:程序员多

  • 零基础的人你让他整共享内存上的多线程
  • 恐怕我们现在用的到处都是 bug 吧???

单线程 + 事件模型

尽可能少但又足够的并发

  • 一个线程、全局的事件队列、按序执行 (run-to-complete)
  • 耗时的 API (Timer, Ajax, …) 调用会立即返回
    • 条件满足时向队列里增加一个事件
$.ajax( { url: 'https://xxx.yyy.zzz/login',
  success: function(resp) {
    $.ajax( { url: 'https://xxx.yyy.zzz/cart',
      success: function(resp) {
        // do something
      },
      error: function(req, status, err) { ... }
    }
  },
  error: function(req, status, err) { ... }
);

异步事件模型

好处

  • 并发模型简单了很多
    • 函数的执行是原子的 (不能并行,减少了并发 bug 的可能性)
  • API 依然可以并行
    • 适合网页这种 “大部分时间花在渲染和网络请求” 的场景
      • JavaScript 代码只负责 “描述” DOM Tree

坏处

  • Callback hell (祖传屎山)
    • 刚才的代码嵌套 5 层,可维护性已经接近于零了

异步编程:Promise

导致 callback hell 的本质:人类脑袋里想的是 “流程图”,看到的是 “回调”。

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

img

Promise: 流程图的构造方法 (Mozilla-MDN Docs)

Promise: 描述 Workflow 的 “嵌入式语言”

Chaining

loadScript("/article/promise-chaining/one.js")
  .then( script => loadScript("/article/promise-chaining/two.js") )
  .then( script => loadScript("/article/promise-chaining/three.js") )
  .then( script => {
    // scripts are loaded, we can use functions declared there
  })
  .catch(err => { ... } );

Fork-join

jsa = new Promise( (resolve, reject) => { resolve('A') } )
b = new Promise( (resolve, reject) => { resolve('B') } )
c = new Promise( (resolve, reject) => { resolve('C') } )
Promise.all([a, b, c]).then( res => { console.log(res) } )

Async-Await: Even Better

async function

  • 总是返回一个 Promise object
  • async_func() - fork

await promise

  • await promise - join

A = async () => await $.ajax('/hello/a')
B = async () => await $.ajax('/hello/b')
C = async () => await $.ajax('/hello/c')
hello = async () => await Promise.all([A(), B(), C()])
hello()
  .then(window.alert)
  .catch(res => { console.log('fetch failed!') } )

总结

本次课回答的问题

  • Q: 什么样的任务是需要并行/并发的?它们应该如何实现?

Take-away message

  • 并发编程的真实应用场景
    • 高性能计算 (注重任务分解): 生产者-消费者 (MPI/OpenMP)
    • 数据中心 (注重系统调用): 线程-协程 (Goroutine)
    • 人机交互 (注重易用性): 事件-流图 (Promise)
  • 编程工具的发展突飞猛进
    • 自 Web 2.0 以来,开源社区改变了计算机科学的学习方式
    • 希望每个同学都有一个 “主力现代编程语言”
      • Modern C++, Rust, Javascript, …
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

4.1-真实世界的并发 的相关文章

  • backward()说明

    1 out backwark 中out是一个标量 此时可以直接使用out backwark import torch from torch autograd import Variable 生成一个内容为 2 3 的张量 Varibale

随机推荐

  • 半角和全角的区别

    很多时候我们阅读代码指导或者其他输入法总是会提到全角和半角 那么他们的区别是啥呢 区别 半角全角主要是针对标点符号来说的 全角标点占两个字节 半角占一个字节 而不管是半角还是全角 汉字都还是要占两个字节 英文半角占一个字节 也就是1byte
  • 六、STL容器:STL仿函数总结

    6 STL仿函数 6 1 概念 模仿函数的类 使用方式如同函数 本质是类中重载括弧运算符operator 6 2 场景 不同函数复用相同处理代码 6 3 使用 6 3 1 C语言的处理方式 使用函数指针和回调函数来实现代码复用 例如qsor
  • 记录的index表介绍

    index表定义 TYPE type name IS TABLE OF element type NOT NULL INDEX BY BINARY INTERGET 例如 1 TYPE emp type array IS TABLE OF
  • 365天挑战LeetCode1000题——Day 264 周赛336

    第一题 遍历 class Solution public int vowelStrings vector
  • python练习实例——金币问题

    目录 题目 解法 输出结果 题目 noip2015 复赛第一题 国王将金币作为工资 发放给忠诚的骑士 第一天 骑士收到一枚金币 之后两天 第二天和第三天 每天收到两枚金币 之后三天 第四 五 六天 每天收到三枚金币 之后四天 第七 八 九
  • sojson jsjiami.com.v6 爬虫js逆向

    sojson jsjiami com v6 爬虫js逆向 地址 aHR0cDovL3d3dy5wYmMuZ292LmNuL3JteWgvMTA1MjA4Lzg1MzIvaW5kZXg1Lmh0bWw 抓取内容 第一次请求 发现返回的不是正确
  • Thinkphp5使用sqlite3作为数据库无法存储小数点的解决方案

    Thinkphp5使用sqlite3作为数据库无法存储小数点的解决方案 在tp的官网搜了一下资料 主要是因为TP5自动绑定内型的时候自动将一些浮点数等类型的数字强制将PDO类型设置为了INT类型 而使用SQLITE数据库时 添加的语句遇到小
  • Centos安装Mysql图文配置详解

    1 安装镜像源 Centos7 MySQL 5 7 yum y install http repo mysql com mysql57 community release el7 rpm MySQL 8 0 yum y install ht
  • MayaToUE4之毛发

    毛发 说明 Maya流程 使用XGen UE4流程 毛发导入预设置 导入毛发 设置骨骼网格体的Groom 毛发材质 毛发物理效果 UE4假发一顶 说明 UE4毛发官方文档 Maya毛发创建XGen导出abc格式 并导入UE4 Maya流程
  • 在使用vnc viewer时候遇到connection refused 10061 111

    在使用vnc viewer时候遇到connection refused 10061 111 解决办法 一般是vncserver 没有启动的问题 在使用CentOS 的时候 可以使用ssh连接远程的服务器 开启vncserver ssh ro
  • Kubernetes 核心概念

    本节课程要点 什么是 Kubernetes 介绍 Kubernetes 的主要功能以及能力 Kubernetes 的架构 介绍 Kubernetes 的核心组件 以及介绍它们之间是如何相互互动连接 Kubernetes 的核心概念与核心 A
  • 安卓期末大作业智慧医疗app-疫苗预约app(附资源链接)

    安卓期末大作业智慧医疗app 疫苗预约app 一 登入页面 下载链接在文末 1 页面和功能展示 2 功能介绍 用户输入用户名和密码后 会和数据库的数据进行匹配 如果账号和密码正确就可以登入到主页面 输入空或者用户名密码错误时会提示错误 密码
  • PS如何快速修改证件照片底色

    PS快速修改证件照片底色 我这里是把蓝色换成红色 工具 原料 电脑 photoshop 方法 步骤 1 打开您的照片 我这个图片来自网络 2 1 选择菜单 选择 色彩范围菜单 2 这时候鼠标变成一个 吸管的形状 哟哦那个吸管在照片的背景上吸
  • java 图片 批量 压缩 +全部压缩

    oldsrc 原图片地址文件夹 如 d newsrc 压缩后图片地址文件夹 如 e widthdist heightdist 压缩后的宽和高 createtime 2010 11 25 auto yijianfeng public void
  • 遇到的bug问题

    1 扩展板的引脚 2 Tone方法 与 红外遥控方法 发生冲突 这个错误提示表面上是说在红外库里定义过的函数在Tone里又重复定义了 实际原因是红外库和Tone都使用了相同的内部计时器TIMER2 在各自的ISR函数里 换名字是不解决根本问
  • [[机缘参悟-87]:每个人需要了解自己的性格特征(老虎、孔雀、考拉、猫头鹰、变色龙)

    我是谁 生肖天性是 老虎 生活习惯是 考拉 后天工作是 猫头鹰 最远距离是 孔雀 心理学改变 变色龙
  • http请求头部各个字段的含义

    前言 我们前几天再做http头部注入的时候 遇到了好多头部 不知道都是啥意思 今天就总结一下 HTTP头字段 英语 HTTP header fields 是指在超文本传输协议 HTTP 的请求和响应消息中的消息头部分 它们定义了一个超文本传
  • clip-path介绍

    clip path介绍 简介 兼容性 基本语法 语法详解 基本图形 inset 基本图形 circle 基本图形 ellipse 基本图形 polygon 使用示例 圆形裁剪 椭圆裁剪 矩形裁剪 多边形裁剪 三角形 菱形 梯形 平行四边形
  • rem与mod的区别

    从老师提供的PPT中复制出来的 感觉还行直接用了 算是转载吧 rem与mod的区别不仔细区分的话 可把rem和mod都当作是求余数的命令 gt gt mod 3 2 ans 1 gt gt rem 3 2 ans 1这两个数的符号一致时的结
  • 4.1-真实世界的并发

    复习 并发编程的基本工具 线程库 互斥和同步 本次课回答的问题 Q 什么样的任务是需要并行 并发的 它们应该如何实现 本次课主要内容 高性能计算中的并发编程 数据中心里的并发编程 我们身边的并发编程 一 高性能计算中的并发编程 高性能计算程