在用户空间中实现线程

有两种主要的方法实现线程包:在用户空间中和在内核中。前者是把整个线程包放在用户空间中,内核对线程包一无所知,仍按正常的单线程进程方式管理。

在用户空间实现线程,线程在一个运行时系统(即线程库)的顶部运行,这个运行时系统是一个管理线程的过程的集合,包括pthread_createpthread_exitpthread_joinpthread_yield等过程。每个进程需要其专门的线程表(thread table),用来跟踪该进程中的线程。线程表由运行时系统管理,当一个线程转换到就绪状态或阻塞状态时,在该线程表中存放重启该线程所需的信息,与内核在进程表中存放的进程的信息完全一样。
当某个线程做了一些会引起在本地阻塞的事情之后,例如等待进程中另一个线程完成某些工作,它调用一个运行时系统的过程,这个过程检查该线程是否必须进入阻塞状态。如果是,它在线程表中保持该线程的寄存器,并查看表中可运行的就绪线程,并把新线程的保存值重新装入机器的寄存器中。只要堆栈指针和程序计数器一被切换,新线程就又自动投入运行。

在用户空间中实现线程包的优点:

(1) 用户级线程包可以在不支持线程的操作系统上实现。 

(2) 线程切换至少要比陷入内核要快一个数量级。在线程完成运行时,它调用thread_yield可以把该线程的信息保存在线程表中;进而,它可以调用线程调度程序来选择另一个要运行的线程。保存该线程状态的过程和调度程序都只是本地过程,所以启动它们比进行内核调用效率更高。另一方面,不需要陷阱,不需要上下文切换,也不需要对内存高速缓存进行刷新,这使得线程调度非常快捷。

(3) 允许每个进程有自己定制的调度算法。

(4) 具有较好的可扩展性,这是因为在内核空间中内核线程需要一些固定表格空间和堆栈空间,当内核线程的数量非常大,就会出现问题。

 

在用户空间中实现线程包的缺点:

(1) 如何实现阻塞系统调用而不影响其它线程。一种方法是使用非阻塞版本的系统调用;另一种方法是使用包装器(jacket wrapper),在进行阻塞系统调用之前检查是否会引起阻塞,如果调用会被阻塞,有关的调用就不进行,代之以运行另一个线程。

(2) 在一个单独的进程内部,没有时钟中断,所以不能用轮转调度的方式调度线程。如果一个线程开始运行,那么在该进程中的其他线程就不能运行,除非第一个线程自动放弃CPU

 

3.2 在内核中实现线程包

在内核中实现线程,此时不再需要运行时系统。另外,每个进程中也没有线程表,相反,在内核中用来记录系统中所有线程的线程表。当一个线程阻塞时,内核可以根据其选择,可以运行同一个进程中的另一个线程,或者运行另一个进程中的线程。而在用户级线程中,运行时系统始终运行自己进程中的线程,直到内核剥夺它的CPU为止。
内核级线程的缺点是:应用程序线程在用户态运行,而线程调度和管理在内核实现。在同一进程中,控制权从一个线程转移到另一个线程,需要用户态-内核态-用户态的模式切换,系统开销较大。
综上:用户级线程和内核级线程之间的差别在于性能。用户级线程的切换需要少量的机器指令,而内核级线程需要完整的上下文切换,修改内存映像,使高速缓存失效,这导致了若干数量级的延迟。另一方面,在使用内核级线程时,一旦线程阻塞在I/O就不需要像在用户级线程中那样将整个进程挂起。

采用混合式线程,内核只识别内核级线程,并对其进行调度。其中一些内核级线程会被多个用户级线程多路复用,在这种模型中,每个内核级线程有一个可以轮流使用的用户级线程集合。

你可能感兴趣的:(在用户空间中实现线程)