高并发中的线程与线程池( 二 )


高并发中的线程与线程池

文章插图
图片
这时,假设我们想写一个程序并且要分利用多核该怎么办呢?
有的同学可能会说不是有进程吗,多开几个进程不就可以了?听上去似乎很有道理,但是主要存在这样几个问题:
  • 进程是需要占用内存空间的(从上一节能看到这一点),如果多个进程基于同一个可执行程序,那么这些进程其内存区域中的内容几乎完全相同,这显然会造成内存的浪费
  • 计算机处理的任务可能是比较复杂的,这就涉及到了进程间通信,由于各个进程处于不同的内存地址空间,进程间通信天然需要借助操作系统,这就在增大编程难度的同时也增加了系统开销
该怎么办呢?
从进程到线程让我再来仔细的想一想这个问题,所谓进程无非就是内存中的一段区域,这段区域中保存了CPU执行的机器指令以及函数运行时的堆栈信息,要想让进程运行,就把main函数的第一条机器指令地址写入PC寄存器,这样进程就运行起来了 。
高并发中的线程与线程池

文章插图
图片
进程的缺点在于只有一个入口函数,也就是main函数,因此进程中的机器指令只能被一个CPU执行,那么有没有办法让多个CPU来执行同一个进程中的机器指令呢?
聪明的你应该能想到,既然我们可以把main函数的第一条指令地址写入PC寄存器,那么其它函数和main函数又有什么区别呢?
答案是没什么区别,main函数的特殊之处无非就在于是CPU执行的第一个函数,除此之外再无特别之处,我们可以把PC寄存器指向main函数,就可以把PC寄存器指向任何一个函数 。
当我们把PC寄存器指向非main函数时,线程就诞生了 。
高并发中的线程与线程池

文章插图
图片
至此我们解放了思想,一个进程内可以有多个入口函数,也就是说属于同一个进程中的机器指令可以被多个CPU同时执行 。
注意,这是一个和进程不同的概念,创建进程时我们需要在内存中找到一块合适的区域以装入进程,然后把CPU的PC寄存器指向main函数,也就是说进程中只有一个执行流 。
高并发中的线程与线程池

文章插图
图片
但是现在不一样了,多个CPU可以在同一个屋檐下(进程占用的内存区域)同时执行属于该进程的多个入口函数,也就是说现在一个进程内可以有多个执行流了 。
高并发中的线程与线程池

文章插图
图片
总是叫执行流好像有点太容易理解了,再次祭出”弄不懂原则“,起个不容易懂的名字,就叫线程吧 。
这就是线程的由来 。
操作系统为每个进程维护了一堆信息,用来记录进程所处的内存空间等,这堆信息记为数据集A 。
同样的,操作系统也需要为线程维护一堆信息,用来记录线程的入口函数或者栈信息等,这堆数据记为数据集B 。
显然数据集B要比数据A的量要少,同时不像进程,创建一个线程时无需去内存中找一段内存空间,因为线程是运行在所处进程的地址空间的,这块地址空间在程序启动时已经创建完毕,同时线程是程序在运行期间创建的(进程启动后),因此当线程开始运行的时候这块地址空间就已经存在了,线程可以直接使用 。这就是为什么各种教材上提的创建线程要比创建进程快的原因(当然还有其它原因) 。
值得注意的是,有了线程这个概念后,我们只需要进程开启后创建多个线程就可以让所有CPU都忙起来,这就是所谓高性能、高并发的根本所在 。
很简单,只需要创建出数量合适的线程就可以了 。
另外值得注意的一点是,由于各个线程共享进程的内存地址空间,因此线程之间的通信无需借助操作系统,这给程序员带来极大方便的同时也带来了无尽的麻烦,多线程遇到的多数问题都出自于线程间通信简直太方便了以至于非常容易出错 。出错的根源在于CPU执行指令时根本没有线程的概念,多线程编程面临的互斥与同步问题需要程序员自己解决,关于互斥与同步问题限于篇幅就不详细展开了,大部分的操作系统资料都有详细讲解 。
最后需要提醒的是,虽然前面关于线程讲解使用的图中用了多个CPU,但不是说一定要有多核才能使用多线程,在单核的情况下一样可以创建出多个线程,原因在于线程是操作系统层面的实现,和有多少个核心是没有关系的,CPU在执行机器指令时也意识不到执行的机器指令属于哪个线程 。即使在只有一个CPU的情况下,操作系统也可以通过线程调度让各个线程“同时”向前推进,方法就是将CPU的时间片在各个线程之间来回分配,这样多个线程看起来就是“同时”运行了,但实际上任意时刻还是只有一个线程在运行 。


推荐阅读