系统中线程类型和常见并发模型
一 并发与并行
- 并发: 同一时间段内,cpu同时处理多条指令(轮询)
- 并行: 同一时刻,cpu同时执行多条指令(同时执行,可以理解多核cpu同时工作)
二 线程类型
根据资源访问权限得不同,操作系统会把内存分为内核空间和用户空间。
内核空间得指令代码能直接调度计算机底层资源(io等);
用户空间则不能直接访问底层资源,需要通过系统调用等方式切换为内核态来实现对计算机底层资源得申请和调度。
线程作为操作系统能调度得最小单位(进程是资源分配的最小单位),也分为用户线程和内核线程
2.1 用户线程
用户线程是由用户空间得代码创建、管理和销毁,线程得调度由用户空间得线程库完成,无需切换内核态,资源消耗少且高效。同一进程下创建得用户线程对cpu得竞争是以进程得维度参与得,导致该 进程下得用户线程只能分时复用进程被分配得cpu时间片,无法很好的利用cpu多核运算的优势。
一般我们说的线程就是用户线程。
2.2 内核线程
内核线程由操作系统管理和调度,直接操作计算机底层资源,线程切换的时候cpu需要切换到内核态。能够很好的利用多核cpu并行计算的优势。开发者可以通过系统调用的方式使用内核线程。
而用户线程是无法被操作系统感知的。
三 并发模型
3.1 用户级线程模型
进程内的多线程是由用户代码完成,使得线程的创建、切换和同步等工作显得很轻量级和高效,但是这些复杂的逻辑需要在用户代码中实现,一般依赖于编程语言层次(比如go从语言层次就支持高并发,而php只能依赖于swoole这样的类库)。
进程内的多线程无法很好地利用cpu多核运算的优势,只能通过分时复用的方式轮换执行。
当进程内任意线程阻塞,比如线程A请求IO阻塞,很可能导致整个进程的阻塞,因为此时进程对应的内核线程因为线程A的IO阻塞而被剥夺CPU执行时间,导致整个进程是去CPU执行权。
3.2 内核级线程模型
进程中的每个线程都对应一个内核线程。
这也导致每次线程切换上下文都会从用户态切换到内核态,产生不小的资源消耗,同时创建线程的数量也会受到操作系统内核创建内核线程数量的限制。
每个线程可以独立被操作系统调度分配到CPU上执行指令,同时某个线程的阻塞并不会影响到进程内其他线程工作的执行,因此可以充分利用CPU多核的能力。
3.3 两级线程模型
介于用户级线程模型和内核级线程模型之间,一个进程将会对应多个内核线程,由进程内的调度器决定进程内的线程如何与申请的内核线程的对应。
进程会事先申请一定数量的内核线程,然后将自身创建的线程与内核线程进行对应(非一对一)。现成的调度和管理由进程内的调度器负责,而内核线程的调度和管理由操作系统负责。
这样就能有效降低线程创建和管理的资源消耗,也能很好的提供并行(和并发)能力。
不过此调度细节对于开发人员来说很是繁琐,比如线程切换时上下文信息的保存和恢复,栈空间大小的管理。
3.4 如何解决3.3的问题?
Go Lang的MPG模型属于一种特殊的两级线程模型,它将CPU、内核线程、线程的关系巧妙地转化为M、P、G的关系,下篇文章我们将讨论GO Lang的MPG模型。