进程、线程及协程

了解进程、线程及协程对前端同学了解 IO 这块帮助极大,对从事 Node 开发的同学更是如此。

1、进程

进程是一种古老而典型的上下文系统,每个进程有独立的地址空间,资源句柄,他们互相之间不发生干扰。

每个进程在内核中会有一个数据结构进行描述,我们称其为进程描述符。这些描述符包含了系统管理进程所需的信息,并且放在一个叫做任务队列的队列里面。

2、进程状态

我们实际上可以把进程状态归结为三个最主要的状态:就绪态,运行态,阻塞(等待)态。这是任何一本系统书上都有的三态转换图。 就绪和执行可以互相转换,基本这是调试的过程。而当执行态程序需要等待某些条件(最典型就是IO)时,就会陷入睡眠态。而条件达成后,一般会自动进入就绪。

进程状态

3、线程

线程是一种轻量进程,实际上在 Linux 内核中,两者几乎没有差别,除了一点——线程并不产生新的地址空间和资源描述符表,而是复用父进程的。

4、协程

有以下这种情况,不断重复网络 IO,然后计算

┌────────────┐     ┌──────────────────────┐
│http request│────▶│   Mass computation   │
└────────────┘     └──────────────────────┘
       ▲                       │           
       └────┐                  └────┐      
            │                       ▼      
┌──────────────────────┐     ┌────────────┐
│   Mass computation   │◀────│http request│
└──────────────────────┘     └────────────┘

协程简单的讲就是在单线程程序遇到 IO 时,由系统处理 IO,线程接下来的计算继续由 CPU 处理。如果用多线程处理上面的情况,往往是单个线程等待 IO,然后另起一个线程由 CPU 继续计算,等 IO 结束,再将上下文切回去,多进程同理。所以,利用协程可以避免线程或者进程切换带来的开销

讲到协程,就难免会想到 JS 的异步,其实 JS 中的异步就是协程。在 JS 执行线程中进行的行为被称作同步 ( Synchronous ) 操作,非 JS 执行线程执行的行为则被称为异步 ( Asynchronous ) 操作。

这里来段 Python 的例子,不用 JS 是因为 Python 写的更有感觉 :)

import asyncio

async def log_task():
    s = 1 + 2 # 仿计算密集型
    print('>>>>>>>>>log begin n sum', s)
    r = await asyncio.sleep(5) # 仿IO密集型
    print('>>>>>>log end')
task = [log_task(), log_task(), log_task()]
asyncio.get_event_loop().run_until_complete(asyncio.wait(task))

#-------- 分割线-------------
# 先由cpu处理
>>>>>>>>>log begin 
 sum 3
>>>>>>>>>log begin 
 sum 3
>>>>>>>>>log begin 
 sum 3

# 等待 5s,系统io结束
>>>>>>log end
>>>>>>log end
>>>>>>log end

当协程发出 IO 指令,会将其状态变更为等待。此时,主线程继续同步执行 Task,待 CPU 计算执行完毕,Evetloop 线程唤起执行 IO。请记住,这里的执行是并发不是并行(并发,不同代码块交替执行(coroutine);并行,不同代码块同时执行)

5、并发与并行

既然讲了,进程、线程这些,并发也并行顺带也讲讲。

一个任务还没执行完,其他任务就开始执行,就是并发。一个任务执行的同时,其他任务同时也在执行,就是并行

并行指物理上同时执行,并发指能够让多个任务在逻辑上交织执行的程序设计。补充其他几个概念:

6、Python 中的多线程以及GIL

要达到并行状态,最简单的就是利用多线程或多进程。但是 Python 的多线程由于存在著名的 GIL,无法让两个线程真正 “同时运行”。例如当一个线程遇到 IO 时,线程挂起,执行权交到另一个线程上,但无论多少线程都只能使用一个核,所以实际上是无法到达并行状态的。

但 Python 多线程是可以并发的,爬虫存在的网络瓶颈对 Python 多线程来讲不存在,不过 Python 爬虫一般考虑使用协程了。关于 GIL,它主要是语言的实现里面,具体的讲,是 C 语言版的 Python。其他语言,即使用了多线程,如果状态同步没有采用 GIL 去做,是不会有 GIL 的问题的。

如果服务器是单核的,多进程没用,只会增进程开销,直接上多线程或者协程。

之前对某个 OA 系统进行过爬取,地址在这里

7、Tips