进程、线程与协程

本文解释进程、线程与协程是什么,以及它们的适用状况,并列举出实际运用例子。html

本文只介绍进程、线程、协程在 Linux(UNIX) 系统下的状况。python

进程

进程是正在执行的程序的实例,包括程序计数器寄存器和变量的当前值。linux

在 Linux 中,进程调用 fork 来建立一个新进程。调用 fork 的称为父进程,建立的新进程称为子进程。nginx

父子进程拥有各自私有的内存映像,也拥有共享的资源:好比打开的文件、一些只读的资源等等。若是一个进程修改了本身的私有内存,另外一个是进程没法看到;若是一个进程修改了可写的共享资源,那么另外一个进程看到的是修改后的资源。mongodb

因为进程数每每大于 CPU 数目,因此 CPU 要在进程之间切换。进程的切换须要保存当前进程的工做,而后加载新进程的工做状态。数据库

进程可分为三种状态:api

  • 运行态(该时刻进程实际占用 CPU。)
  • 就绪态(可运行,但由于其余进程正在运行而中止。)
  • 阻塞态(除非某种外部事件发生,不然进程不能运行)

当全部进程处于阻塞态时,CPU 会空转,从而形成浪费,这种状况下采用多进程能够提升 CPU 的利用率。可是因为建立进程须要消耗其余资源(好比内存),因此进程数不能无限大,要选择一个数值使总体资源的消耗率最大。服务器

线程

在传统操做系统中,每一个进程有一个地址空间和一个控制线程。进程把相关的资源集中起来存放到本身的地址空间中以便管理,而线程则是在 CPU 上被调度执行的实体。进程拥有一个执行的线程,线程中有一个程序计数器,记录接下来要执行的命令。线程拥有寄存器,用来保存线程当前的工做变量。线程还拥有一个堆栈,用来记录执行的历史。网络

多线程是在一个地址空间中运行的多个控制线程。除了共享地址空间和其余一些资源,这些线程与进程差很少。多线程

进程中的内容:

  • 地址空间
  • 全局变量
  • 打开文件
  • 子进程
  • 即将发生的报警
  • 信号与信号处理程序
  • 帐户信息

线程中的内容:

  • 程序计数器
  • 寄存器
  • 堆栈
  • 状态

以上“进程中的内容”是全部线程共享的,而“线程中的内容”是每一个线程独立的。

使用多线程的主要缘由是它们共享一个地址空间和全部可用数据,而因为多进程具备不一样的地址空间,因此多进程不能共享地址空间(可是多进程也能够经过共享内存来通讯)。因此对于没有相互之间没有联系的线程,使用多进程单线程;对于相互合做,共同完成一个做业的线程,使用单进程多线程。

因为线程共用了进程的不少东西,因此线程比进程更加轻量,所以建立、撤销地更快,在许多系统中,线程比进程的建立快 10 ~ 100 倍。

Linux 的线程是内核线程,因此 Linux 系统的调度是基于线程的,因此多线程也能够利用多核 CPU。

线程局部变量

对一个线程来讲是可见的,但对于其余线程是不可见的。

好比由操做系统维护的 errno。线程1执行 access 以肯定它是否容许访问某个文件。操做系统把返回值放到 errno 里。当控制权返回线程1并在线程1读取 errno 以前,调度程序确认线程1已用完 CPU 时间,并决定切换到线程2。线程2执行一个 open 调用,结果失败,致使重写 errno,因而给线程1的 errno 会永久丢失。

解决办法是引入一个新的库过程,以便建立、设置和读取这些线程范围的全局变量。该调用在堆上或线程专用储存区上分配一个储存空间用来作这个事情。

好比在 Python 中,有 threading.local()

为何用户态线程比内核线程快?

根据 Difference Between System Call, Procedure Call and Function Call

当一个用户程序触发一个系统调用,会在用户程序和内核之间发生上下文切换。这意味着用户程序中止执行并保存它的状态(推到栈上)。内核被调入以完成任务。结果返回到调用的用户程序而且内核被调出。

而用户态线程须要保存信息到该线程的寄存器,查看线程表中的就绪线程,并把新线程的信息装入寄存器中。只要堆栈指针和程序计数器一被切换,新的线程又自动投入运行。相比陷入内核而言上下文切换要少不少,开销要小,因此更快,更快的程度是一个数量级或者更多。

那么既然用户态更快,为何不将进程放到用户态中?

多进程(线程)问题

  • 进程间通讯
  • 两个或多个进程在关键活动中不会出现交叉
  • 正确的顺序

因为线程共享地址空间,因此第一个问题很好解决,但线程也存在后两个问题。因此本章中进程的问题、解决方案一样也适用于线程。

进程间通讯

经过进程间的公用储存区:好比内存或文件。

竞争条件

多个进程读写某些共享数据,结果取决于进程的运行顺序。

好比 A、B 进程同时向帐户金额为 1000 的帐户增长 100。A 进程读取到帐户金额为 1000,增长 100 后获得 1100,在将 1100 写入帐户前,CPU 调度程序将 A 换下将 B 换上。

B 读取到帐户金额为 1000,加 100 后设置回去,帐户金额变为 1100。B 进程执行完毕,切换到 A 进程,A 进程继续运行,将帐户金额设置为 1100,结果帐户少加了钱。

共享数据进行访问的程序片断称做临界区。

凡是涉及共享数据的状况都会引起竞争条件。为了不这个错误,咱们要阻止多个进程同时读写共享的数据。即确保,当一个进程在使用一个共享数据的时候,其余进程不能使用该共享数据。

方法是,当一个进程想进入临界区时,先检查是否容许进入,若不容许,则该进程将进入阻塞态,直到其收到临界区可用的信号。

忙等待

进程反复检查一个条件是否为真。

忙等待会使 CPU 空转,浪费 CPU,但若是操做者知道忙等待花费的时间会很短,那么能够考虑使用。好比 TSL 的信号量操做,仅需几个毫秒,这与生产者等待缓冲区腾出是彻底不一样的时间级别。

用户线程中,因为没有时钟中止运行时间过长的线程。结果是忙等待的线程将永远循环下去,毫不会获得锁,由于这个运行的线程不会让其余线程运行从而释放锁。因此线程获取所获取锁失败后,能够调用 thread_yield 将 CPU 放弃给另外一个线程,这样就没有忙等待,在该线程下次运行时,它将再次对锁进行测试。

协程

协程和线程相似,有本身的程序计数器、寄存器、堆栈、状态,和其余协程共享全局变量等资源。协程和线程的区别是,线程能够并行而协程是协做的:在任什么时候刻,只有一个协程在运行。而且一个协程会一直运行除非它主动放弃控制权并将其转移给另外一个协程。[4]

协程和线程

与线程相比,协程是放弃 CPU 并将其给另外一个协程[2],而线程只是放弃 CPU[3]。

协程和生成器

二者均可以屡次放弃 CPU,暂停它们的执行而且从新从暂停处执行,区别是协程能够马上控制它放弃后的执行,而生成器不能够,生成器将控制权还给了生成器的调用者。生成器中的 yield 不会指定要跳到哪一个协程而是将一个值传回上一级。

使用场景

uWSGI

uWSGI 能够设置进程数和线程数。可是并无说明在什么状况下适用。

Nginx[5]

在基于进程或线程处理并发链接的模型中,使用单独的进程或线程来处理每一个链接,而且会阻塞在网络或输入、输出操做上。因此 CPU 和内存的利用率低。产生进程或线程须要预先准备运行环境,包括分配内存给堆和栈以及建立新的执行环境。建立这些项目也须要消耗额外的 CPU,而且因为线程频繁的上下文切换,最终会致使不好的性能。Apache 就是使用以上模型。

Nginx 采用模块化、事件驱动、异步、单线程、非阻塞的架构。在一个高效的 run-loop 单线程进程中处理链接,这个进程叫作 worker。

因为 nginx 产生多个 workers 去处理链接,因此能够利用多核 CPU。多个 workers 之间经过共享内存交流。

应该根据磁盘使用和 CPU 使用模式来调整 nginx 的 workers 数量。若是是 CPU 密集型工做,好比处理不少 TCP/IP 链接、处理 SSL 或者压缩,nignx 的 workers 应该与 CPU 核心数一致;若是大部分是 IO 密集型工做,好比从磁盘中提供不少内容,那么 wokers 数量应该增多,具体取决于 IO 工做的具体状况,好比等待时间、磁盘储存类型等。

数据库链接

数据库链接是由客户端驱动决定的,这里以 pymongo 和 motor 做为例子。

pymongo[6]

每一个 MongoClient 实例都有一个内置的链接池。链接池按照需求打开 sockets 来支持多线程应用对 MongoDB 的并发操做。

链接池的最大数目是 maxPoolSize,默认为 100。若是 maxPoolSize 的链接正在被使用,那么下一个请求会一直等待直到有链接可用。

MongoClient 实例会打开一个额外的 socket 来监控 MongoDB 服务器的状态。

motor[7]

不支持多线程和 fork 操做;Motor 只能被用在单线程的应用,好比 Tornado 中。

参考

  1. 现代操做系统(第3版)- Andrew S·Tanenbaum 第二章 进程与线程
  2. https://en.wikipedia.org/wiki/Coroutine
  3. http://man7.org/linux/man-pages/man3/pthread_yield.3.html
  4. Programming in Lua - "Coroutines" section
  5. http://www.aosabook.org/en/nginx.html
  6. http://api.mongodb.com/python/current/faq.html#how-does-connection-pooling-work-in-pymongo
  7. https://motor.readthedocs.io/en/stable/differences.html#threading-and-forking
相关文章
相关标签/搜索