AlexiaChen.github.io
AlexiaChen.github.io copied to clipboard
为什么使用多线程在大多数情况下是个坏注意?
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上性能往往比多线程的程序性能更好。
-
事件驱动的程序比多线程程序更好移植。
-
多线程程序才能真正利用多核
你应该放弃多线程吗
- 不能,在高性能服务器上必须有效利用多线程,比如数据库软件。