☕【并发技术系列】「多线程并发编程」技术体系和并发模型的基础探究(夯实基础)

让我们通过本篇文章一同进入并发编程技术的世界里面,相信通过这篇文文章一定会对话你的并发技术体系有一定帮助以及夯实你的基础功底。

基本概念

  • 并发concurrency
  • 并行parallelism
  • 吞吐量throughput

并发操作处理机制

并发:CPU划分时间片,轮流执行每个请求任务,时间片到期后,换到下一个

image

并行操作处理机制

并行:在多核服务器上,每个CPU内核执行一个任务,是真正的并行

image

吞吐量

单位时间内服务器总的请求处理量

  • 以 request/second 来衡量,如1200rps
  • 每个请求的处理时间latency
  • 服务器处理请求的并发workers
  • 其他因素如GC也会影响吞吐量

CSDN new bbs 的案例

  • 平均每个请求的latency – 200ms
  • 总共40个workers
  • 理论吞吐量上限 1000/200*40 = 200rps
  • 理论每日处理动态请求上限1700万,目前实际每日处理动态请求270-330万,预估实际处理上限600万

IO类型

  • 磁盘文件操作,例如读硬盘文件
  • 操作系统调用,例如shell命令
  • 网络操作
    • 访问数据库 MySQL, MongoDB, ...
    • 访问其他Web服务,发起网络连接
    • 访问缓存服务器 Memcached, Redis
IO密集请求

IO操作的延时远远高于CPU时钟周期和内存访问,所以一旦Web请求涉及IO操作,CPU处于wait状态,被浪费了。

image
IO密集型并发

并发真能提高吞吐量吗?

假设每个请求执行100ms,顺序执行10个请求共需要1s
单核服务器并发处理10个请求,假设平均分配时间片10ms,请求1到请求10将在900ms到1000ms间执行完毕。

顺序执行10个请求,每个请求100ms,总共1s执行完毕

image

并发执行10个请求,每个请求分配10ms的时间片,仍然1s执行完毕
吞吐量没有提高,每个请求处理时间变长。

吞吐量没有任何提高。并发越多,所有请求都变得非常缓慢。(考虑到任务的场景切换开销,吞吐量还会下降,需要超过1s才能执行完毕)。

大多数Web型应用都是IO密集型

  • 并发执行10个请求,每个请求分配10ms的时间片

  • 200ms之后CPU处于空闲状态

  • 执行请求100ms当中,可能有80ms花在IO上,只有20ms消耗CPU时钟周期,最好情况下,请求1到请求10将在190ms到280ms间执行完毕,吞吐量极大提高。

image
  • IO密集型应用,大部分CPU花在等待IO上了,所以并发可以有效提高系统的吞吐量

并发和并行

纯CPU密集型的应用

  • 在单核上并发执行多个请求,不能提高吞吐量
  • 由于任务来回场景切换的开销,吞吐量反而会下降
  • 只有多核并行运算,才能有效提高吞吐量

IO密集型的应用

由于请求过程中,很多时间都是外部IO操作,CPU在wait状态,所以并发执行可以有效提高系统吞吐量。

并发模型模型发展

  • multi-process(多进程)
  • multi-thread(多线程)
  • multi-process + multi-thread(GIL)(多进程+多线程)
  • event I/O(事件驱动)
  • coroutine(协程)

常见多进程Web服务端编程模型

  • PHP
  • Python
  • Ruby

多进程优点

  • 并发模型非常简单
    • 由操作系统调度运行稳定强壮
  • 非常容易管理
    • 很容易通过操作系统方便的监控,例如每个进程CPU,内存变化状况,甚至可以观测到进程处理什么Web请求很容易通过操作系统管理进程,例如可以通过给进程发送signal,实现各种管理: unicorn。
  • 隔离性非常好
    • 一个进程崩溃不会影响其他进程
    • 某进程出现问题的时候,只要杀掉它重启即可,不影响整体服务的可用性
    • 很容易实现在线热部署和无缝升级
  • 代码兼容性极好,不必考虑线程安全问题
  • 多进程可以有效利用多核CPU,实现并行处理
多进程监控
  • 监控进程CPU top –p pid
    • 简单处理甚至可以查看进程处理的URL请求
  • 监控进程的IO iotop –p pid
  • 监控进程的物理内存使用 ps, /proc

多进程缺点

内存消耗很多

每个独立进程都需要加载完整的应用环境,内存消耗超大。(COW模式可以缓解这个问题)

例如每个Rails进程物理内存占用为150MB,20个workers,则需要3GB物理内存。

CPU消耗偏高

多进程并发,需要CPU内核在多个进程间频繁切换,而进程的场景切换(context switch)是非常昂贵的,需要大量的内存换页操作。

很低的I/O并发处理能力
  • 多进程的并发能力非常有限

    • 每个进程只能并发处理1个请求
    • 单台服务器启动的进程数有限,并发处理能力无法有效提高
  • 只适合处理短请求,不适合处理长请求

    • 每个请求都能在很短时间内执行完毕,因而不会造成进程被长期阻塞一旦某个操作特别是IO操作阻塞,就会造成进程阻塞
    • 当大面积IO操作阻塞发生,服务器就无法响应了
    • 对于无法预知的外部IO操作,应用代码必须设置timeout参数,以防进程阻塞
缓解多进程低IO并发问题
  • 用nginx做前端Web Server

    • 适当增大proxy buffer size,避免多进程request/response buffer IO开销

    • 使用X-sendfile,避免多进程读取大文件IO开销

  • 凡IO操作都要设置timeout

    • 避免无法预知的IO挂起造成进程阻塞
  • 长请求和短请求分离开,不要放在一起

multi-thread多线程操作模型

常见多线程模型(1:1)

1 native thread : 1 process thread

  • 在一个重量级进程当中启动多个线程并发处理请求

  • 多线程并发

    • 每个线程可以并发处理1个请求,并发能力取决于线程数量线程的调度由VM负责,可以通过编程控制
多线程优点

多线程并发内存消耗比较少

  • 每个线程需要一个thread stack保存线程场景,thread stack一般只需要十几到几十KB内存,不像多进程,每个进程需要加载完整的应用环境,需要分配十几到上百MB内存。
  • 线程可以共享资源,特别是可以共享整个应用环境,不必像多进程每个进程要加载应用环境。

多线程并发CPU消耗比较小

  • 线程的场景切换开销小于进程的场景切换

很容易创建和高效利用共享资源

  • 数据库线程池
  • 字典表,进程内缓存......

IO并发能力很高

  • Java VM可以轻松维护几百个并发线程的线程切换开销,远高于多进程单服务器上几十个并发的处理能力

可有效利用多核CPU,实现并行运算

多线程的缺点

VM的内存管理要求超高

  • 对内存管理要求非常高,应用代码稍不注意,就会产生OOM(out of memory),需要应用代码长期和内存泄露做斗争
  • GC的策略会影响多线程并发能力和系统吞吐量,需要对GC策略和调优有很好的经验
  • 在大内存服务器上的物理内存利用率问题

对共享资源的操作

  • 对共享资源的操作要非常小心,特别是修改共享资源需要加锁操作,很容易引发死锁问题

应用代码和第三方库都必须是线程安全的

  • 使用了非线程安全的库会造成各种潜在难以排查的问题

单进程多线程模型不方便通过操作系统管理

  • 一旦出现线程死锁或者线程阻塞很容易导致整个VM进程挂起失去响应,隔离性很差

multi-thread with GIL

  • Global Interpeter Lock:有限制的并发
  • IO操作或者操作系统调用,释放锁,多线程IO并发
  • 由于加锁,无法利用多核,只能使用1个CPU内核,因而无法实现多核并行运算

提供简化的并发策略

对CPU密集型运算,并发不能提高吞吐量:加锁,禁止并发
对IO密集型运算,并发可以有效提高吞吐量:解锁,允许多线程并发

性能

对CPU密集型运算,多线程并发由于线程场景切换带来的开销,吞吐量要差于单进程顺序执行

兼容性

加锁可以保证代码和库的兼容性

image

multi-process + multi-thread(GIL)

  • 由于GIL,多线程只能跑在1个CPU内核上,无法有效利用多核CPU,跑多个进程可以有效利用多核,一般进程数略多于服务器CPU内核数
  • 一个进程不宜跑过多线程,否则会引发严重的GC内存管理问题

pros and cons

  • 内存消耗低于单纯的多进程并发
  • 非常有效的提高了IO并发处理能力
  • IO库和操作系统调用库必须保证线程安全

event IO

常见event IO编程模型

  • Nginx / Lighttpd
  • Ruby EventMachine / Python Twisted
  • node.js

event IO原理

  • 单进程单线程
  • 内部维护一个事件队列
  • 每个请求切成多个事件
    • 每个IO调用切成一个事件
    • 编程调用process.next_Tick()方法切分事件
  • 单进程顺序从事件队列当中取出每个事件执行下去
image

event IO的优点

惊人的IO并发处理能力
  • nginx单机可以处理50K以上的HTTP并发连接
  • node.js单机可以处理几千上万个HTTP并发连接
极少的内存消耗

单一进程单一线程,无场景切换无需保存场景

CPU消耗偏低

无进程或者线程场景切换的开销

event IO的缺点

必须使用异步编程

异步编程是一种原始的编程方式
代码量和复杂度都会有很大的增加,提高了编程的难度,以及开发和维护成本
复杂的业务逻辑(例如工作流业务)会造成代码迅速膨胀,极难维护
异步事件流使得异常处理和调试有很大困难

CPU密集型的运算会阻塞住整个进程

需要通过编程,将密集型的任务拆分为多个事件

所有IO操作必须使用异步库

一旦不小心使用同步IO操作,会造成整个进程阻塞,库的兼容性必须非常小心

只能跑在1个CPU内核上,无法有效利用多核并行运算

运行多个进程来利用多核CPU

coroutine原理

  • 在单个线程上运行多个纤程,每个纤程维护1个context
  • 纤程非常轻量级,单个线程可以轻易维护几万个纤程
  • 纤程调度依赖于应用程序框架
  • 纤程切换
    • 必须自己编程来实现
    • 一般应用层代码不需要编程,框架层实现纤程调度
  • 纤程本质上是基于event IO之上的高级封装,但消除了event IO原始的异步编程复杂度
image

单一线程通过程序调度了3个纤程并发,底层仍然是event IO驱动
但是有3个清晰的并发执行体,仍然是同步并发编程风格,但实现了异步驱动

coroutine的优点

  • 支持极高的IO并发,和event IO基本相当
  • 纤程的创建和切换的系统开销非常小,CPU和内存消耗都很小
  • 编程方式和常见的同步编程基本一致,是event IO的高级封装形式

coroutine的缺点

  • 纤程运行在单线程上,无法有效利用多核实现并行运算
    • 通过启动多个进程或者多个线程来利用多核CPU
  • CPU密集型的运算会阻塞住整个进程
    • 通过编程,将密集型的任务拆分为多步
  • 所有IO操作必须使用异步库
    • 一旦不小心使用同步IO操作,会造成整个进程阻塞,库的兼容性必须非常小心

参考资料

  1. multithreaded-rails-is-generally-better
  2. threads-in-ruby-enough-already
  3. about-concurrency-and-the-gil
  4. the-ruby-global-interpreter-lock
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容