目的
我们使用多线程通常有两点目的:
目的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自称单线程语言也并无不妥。