经典线程模型

对于进程来讲,它将进程本身运行所需要的众多资源都包含在了进程的地址空间中,对他们加以管理,来保证进程的正常运行。而在进程中,真正被调度并执行一定逻辑的却是线程。一个进程被创建之后,至少有一个线程,这个线程叫做主线程。其实,借用上一篇文章讨论进程和线程的观点,线程确实可以被当做一个迷你版的进程。试想一下,多个进程运行在操作系统内,共享的资源是硬盘,cpu时间,内存等。那么多个线程在进程的地址空间内,共享的资源是操作系统分配给他们所属进程的资源。唯一一点不同的就是,进程和进程之间是相互独立的,但是多个线程之间确是在同一个进程的地址空间内的。

既然多个线程在同一个地址空间内,那么对共享的资源进行读写的时候,肯定会涉及到互斥的问题。但是操作系统给出的答案是不可能+不需要。操作系统认为,同一个进程中的多个线程为的是合作,而不是竞争。如果明确的存在竞争关系,那么还是应该借用多进程的处理模式,而不是多线程。但是在实际的开发过程中,我们知道,线程安全是你代码鲁棒性的一个重要因素,我想,操作系统的设计也是想让这部分我们自己来管理,而不是在底层就做好,假想用户的需求。

如何实现线程

用户态

优势

在用户态中实现线程以及相关的操作,一个最大的好处就是,这种方式可以在很多系统层面上不支持线程的操作系统中运行起来。在用户态中实现线程,通常有一个概念叫做runtime system。runtime管理着进程中线程的各种行为,如创建,销毁,调度等。runtime system还负责维护一张线程表,它和操作系统级别的进程表是一样的,保存了线程的各种信息。并且他还包含了一系列管理线程操作的集合,如:pthread_create, pthread_exit等。

另外一个优点就是,线程的很多操作,都可以在本地完成,不需要内核额外的支持,频繁的使用系统调用效率肯定会降低。所以在线程的操作都可以在runtime系统中进行管理的前提下,彼此之间的工作效率会比较高。并且,在用户态中实现线程,如果有一些想要扩展的功能,在用户空间扩张一些信息表以及堆栈是很方便的。相对内核来讲,由于线程的数量众多,一旦堆栈数量一多事非常麻烦的。

劣势

在用户空间实现线程的劣势,其中一个就是使用阻塞类的系统调用。线程概念的出现,就是让多个不同的线程可以配合起来一起工作。其中一个线程因为一些原因不能运行的话,runtime system就会调度另外一个线程执行。但是如果一个线程使用了一个阻塞的系统调用,那么结果将是灾难性的。这次的调用不仅仅阻塞了使用这个系统调用的线程,它是阻塞了整个进程的,也就是说阻塞了这个进程中的所有线程。这样一来就违背了线程带给我们的好处。其中一个解决办法就是将所有的阻塞系统调用改成非阻塞的,这是比较暴力的一种方法,需要改进操作系统。另外一个就是将阻塞的系统调用换成select系统调用+原始阻塞调用的集合。先通过select来判断预期的阻塞调用是否真的因为条件不满足而会被阻塞住,如果会,那么就通知runtime system来调用另外一个线程执行。等到下一次轮到那个线程的时候,再去使用select来进行检验即可。但是这样一来,一个简单的系统调用的问题就会涉及到很多复杂的更改,而且效率也很低。

另外一个劣势,是由缺页中断引起的。其实缺页中断并不能算是一个劣势,只不过在用户态实现线程的时候,如果某一个线程引起的缺页中断,但是系统却只认识其所在的进程,那么很自然,系统会阻塞整个进程,直到i/o结束,缺失内存页中的内容从硬盘中加载进来。

第三个要说明的劣势,是由runtime system包含的对线程处理的过程集合引起的。因为操作系统不认识多线程,只认识多进程。那么在线程的调度上面就没办法借助操作系统进行时间片轮转的处理方式。用户态中,线程之间的调度,如果不是从线程内部发出一个信号,如thread_yield或者thread_join等,从外部是没办法来直接从一个线程切换到另外一个线程上的。 }}

内核态

劣势

在内核态实现线程最大的劣势,就是开销大。内核在处理进程的调度以及创建和销毁的时候,开销其实就是不小的。加之线程的数量远远超过进程的数量,一方面是切换,创建,销毁这类的操作比较耗费资源,还有一方面就是,之前在用户空间中维护的线程相关的信息,如线程表等将都会由内核来维护。

优势

优势也很明显,无论一个线程是因为阻塞的系统调用,还是因为缺页中断等原因。内核都会检查其所在进程以及其他进程中的线程有无可以调度运行的,如果有的话,那么就可以进行调度,而不会影响整个进程的运行。

混合实现

其实不光是操作系统,很多现实生活中的事情也是一样。两种方法各有优劣,那么就将二者的优点结合起来,但是将缺点减半。混合实现的方式便是内核线程和用户线程两者结合起来使用。一个内核线程对应一个用户线程的集合。内核线程通过操作系统来调度处理,但是用户线程通过用户空间本身来进行调度处理。这样一来,既降低了系统资源的开销,也解决了用户态实现线程出现的一些问题。