共计 1104 个字符,预计需要花费 3 分钟才能阅读完成。
目的
我们使用多线程通常有两点目的:
目的 1:充分利用多核优势。现在无论是终端设备还是服务器,基本上都是跑在多核 CPU 上的,如果想要充分利用多核性能,上层应用的线程数至少不能少于 CPU 核心数。
目的 2:并发执行一些逻辑。最常见的就是,耗时逻辑应避开 UI 线程。
线程数
需要注意的是,线程数并非越多越好。当活跃线程数超出 CPU 核数时,会带来上下文切换开销。且子线程还会带来一定的内存开销(iOS 平台下,主线程占用 1M 内存,每个子线程会占用 512K 内存)。
理想情况下,线程数和 CPU 核心数应当大体在同一数量级。(部分平台有人推荐 CPU 核心数 x2 作为线程池大小)。iOS 中,GCD 的并发队列,如果不存在阻塞的话,所使用的线程数即 CPU 核心数(参考[Number of threads created by GCD?)。
多线程和异步
印象中,早年很多程序,是开启一个子线程去发网络请求,这个子线程会阻塞等待网络请求返回并处理结果。
这种方式称为 同步 请求,期间这个线程是 阻塞 的。
现在这样做的少了,通常发出网络请求后当前线程会继续往下执行,请求回包后会通过某种事件处理机制触发回调函数进行处理。
这种方式称为 异步 ,期间这个线程是 非阻塞 的。
除了网络请求,IO 读写等场景也是类似的。这些不占用 CPU 的场景应当优先使用异步的手段,而不是开子线程处理。(iOS 平台下好像基本上没有这样做的,年轻时写 Java 的时候倒是干过不少 …)
代价
多线程的好处,其实就是最开始的两点目的。但实际开发中,由于多个线程访问同一份内存数据,很容易产生冲突,往往就得加锁。忘了加锁就容易 crash,锁加的不好就会有性能问题(→_→ 参考 python 的 GIL)。线程、逻辑、数据之间的关系,最好是能在程序 / 模块设计之初就明确下来,但实际开发中往往没这么理想化。总的来说,多线程给程序员带来了大量的心智负担,也比较容易出问题。
非传统方案
面对传统多线程编程的各种问题,业界提出了一些解决方案。主要思想是:
- 尽量不用多线程,部分场景可以用多进程替代。例如服务端的 PHP 和 nodejs。
一定要用多线程的话,通过 通信 机制共享数据而不是直接 共享内存。
- 比如 GO:Do not communicate by sharing memory; instead, share memory by communicating.
- 比如 js 的 web worker:Web Worker 使用教程
- dart 的 isolate 机制:dart 基础之异步编程
其实抛弃了共享内存的多线程,已经跟传统意义的多线程没有太多关系了。比如 dart 的 isolate 机制,虽然也可以说是不提供共享内存只提供通信机制的多线程,但是 dart 自称单线程语言也并无不妥。