AlexiaChen.github.io icon indicating copy to clipboard operation
AlexiaChen.github.io copied to clipboard

为什么使用多线程在大多数情况下是个坏注意?

Open AlexiaChen opened this issue 6 years ago • 0 comments


title: 为什么使用多线程在大多数情况下是个坏注意? date: 2018-03-20 14:00:00 tags:

  • 线程

简介

什么是线程?

  • 诞生并成长于操作系统这个世界中
  • 在用户级别的工具中慢慢演进
  • 被提议为多种多样的问题的一种解决方案

那么,上面说的如此明确了,我们是否在现实工作中成为一个彻头彻尾的“多线程程序员”呢?

多线程带来的问题: 多线程程序很难编写

相对于多线程的一个折中方案: 采用事件驱动的思想

声明:

  • 对于大多数的场景,事件要比多线程更好
  • 多线程一般只能用于真正确实需要用到CPU并行的场景

多线程的使用场景

  • 操作系统: 一个内核线程对应一个用户进程

  • 科学计算: 一个线程可以绑定到一个CPU上执行,有效利用多核,提高性能。(比如大规模非线性方程组运算等等)

  • 分布式系统: 大量进程的并发请求(IO复用)

  • GUI编程: 线程对应用户的Action(按钮点击等)。比如需要一个后台线程长时间做运算,并最终显示结果在GUI上。

  • 多媒体,动画: 类似于GUI

为什么多线程很难

  • 同步:访问共享数据的时候都要加锁。忘记加锁了?对不起,数据损毁。

  • 死锁: 多线程访问多个锁,锁之间有循环依赖。多个进程互相等待,造成系统挂起

  • 难以Debug: 数据依赖,操作时序依赖

  • 多线程破坏了抽象: 不能设计独立强的模块

  • 回调函数不能与锁一起良好进行工作

  • 很难达到理想性能:锁的粒度控制不好容易造成并发度降低,操作系统的调度和上下文切换机制限制了软件性能(用户态切换到内核态的时间是很昂贵的)

  • 多线程不被良好支持: 操作系统提供的原生线程API不兼容,难以移植。

事件驱动编程

  • 单个执行流: 没有CPU并行
  • 可以注册感兴趣的事件(通过回调函数)
  • Event-loop等待事件,并调用对应的event handler
  • event handler之间没有优先级(内核线程调度有优先级)
  • event handler的生命周期很短 (内核线程创建销毁耗时)

事件驱动编程被用在哪些场景

  • 大多数的GUI框架,按钮响应等对应一个Event,一个Event对应一个Event Handler。一个Event Handler可以实现一些行为,打开文件,删除等等

  • 分布式系统。一个handler对应每个输入源(Socket),handler处理请求和回送响应。事件驱动型的IO复用。

事件驱动带来的问题

  • handler如果进行长时间的运算,会导致程序长时间无响应(因为是单线程执行流,比如node.js)。对于这种耗时的运算,可以fork一个子进程来处理,子进程通过事件来通知主进程自己的完成情况。

  • 不能跨事件的维护一个local state。(handler必须返回)

  • 单线程执行流不能利用多核,也就是不适用于科学计算软件

事件VS多线程

  • 事件尽可能避免了并发,多线程相反。事件驱动更好入门,没有数据竞争和死锁。多线程可能对于一个简单场景用起来都更复杂。

  • 事件驱动的程序更好Debug。时序依赖只跟事件有关,没有内部调度。

  • 事件驱动在单核CPU上性能往往比多线程的程序性能更好。

  • 事件驱动的程序比多线程程序更好移植。

  • 多线程程序才能真正利用多核

你应该放弃多线程吗

  • 不能,在高性能服务器上必须有效利用多线程,比如数据库软件。

AlexiaChen avatar Oct 16 '19 07:10 AlexiaChen