我们都知道有线程和进程,简单说:一个进程可以有多个线程,进程由系统进行调度,而线程则有软件(也就是编写者)进行调度。

线程与进程的区别主要有:

  • 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
  • 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
  • 线程是处理器调度的基本单位,但进程不是
  • 二者均可并发执行
  • 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

线程和协程的区别主要有:

  • 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
  • 线程进程都是同步机制,而协程则是异步 。
  • 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
  • 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
  • 协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行。
  • 线程是协程的资源。协程通过Interceptor来间接使用线程这个资源。

下面来介绍协程。

首先需要了解一些概念:

  • event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就去调用对应的处理方法。
  • coroutine:中文翻译叫协程,在python中常指代携程对象类型,我们可以将协程对象注册到时间循环中,他会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立刻被执行,而是会返回一个协程对象。
  • task:任务,这是对协程对象的进一步封装,包含协程对象的各个状态。
  • future:代表将来执行或者没有执行的任务的结果,实际上和task没有本质区别。

了解概念后,我们编写一些代码来更深层次了解协程:

import asyncio
async def execute(x):
    print('Number:', x)
coroutine = execute(1)
print('Coroutine:', coroutine)
print('after calling function')
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
print('after calling loop')

运行结果:

Coroutine: <coroutine object execute at 0x000001FA66112780>
after calling function
Number: 1
after calling loop

可以看出,刚开始调用execute(1) 并没有进行输出,而是返回了一个Coroutine对象,而在运行get_event_loop后才执行了函数。

可见,async定义的方法会变成一个无法执行的协程对象,必须将此对象注册到事件循环中才可执行。

前面我们还提到了task,它是对协程对象的进一步封装,比协程对象躲了运行状态,例如running,finished等,我们可以利用这些状态获取协程对象的执行情况。

在上面的例子中,我们把协程对性爱那个coroutine传递给run_until_complete的时候,实际上是对它进行了一个操作,就是将coroutine封装成了task对象。对此,我们也可以显式的声明:

import asyncio
async def execute(x):
    print('Number:', x)
    return x
coroutine = execute(1)
print('Coroutine:', coroutine)
print('after calling funtion')
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
print('Task:', task)
loop.run_until_complete(task)
print('Task:', task)
print('after calling loop')

输出:

Coroutine: <coroutine object execute at 0x000002527FF62780>
after calling funtion
Task: <Task pending coro=<execute() running at ..../coroutines.py:55>>
Number: 1
Task: <Task finished coro=<execute() done, defined at..../coroutines.py:55> result=1>
after calling loop

我们可以看到协程的两种转态:pending和finished。

我们当然也可以为task绑定一个回调函数:

import asyncio
async def execute(x):
    print('Number:', x)
    return x
def callback(task):
    print('Status:', task.result())
coroutine = execute(1)
print('Coroutine:', coroutine)
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
task.add_done_callback(callback)
print('Task:', task)
loop.run_until_complete(task)
print('Task:', task)

输出:

Coroutine: <coroutine object execute at 0x0000014BFAF93780>
Task: <Task pending coro=<execute() running at ..../coroutines.py:75> cb=[callback() at ..../coroutines.py:80]>
Number: 1
Status: 1
Task: <Task finished coro=<execute() done, defined at ..../coroutines.py:75> result=1>

可以看到,完成后的callback函数已经被执行。

现在,我们只执行了一次函数。那么,如果如何执行多次呢?

import asyncio
async def execute(x):
    print('Number:', x)
    return x
tasks = [asyncio.ensure_future(execute(i)) for i in range(5)]
print('Task:', tasks)
loop = asyncio.get_event_loop()
task = loop.create_task(asyncio.wait(tasks))
loop.run_until_complete(task)

输出:

Task: [<Task pending coro=<execute() running at ..../coroutines.py:75>>, <Task pending coro=<execute() running at ..../coroutines.py:75>>, <Task pending coro=<execute() running at ..../coroutines.py:75>>, <Task pending coro=<execute() running at..../coroutines.py:75>>, <Task pending coro=<execute() running at ..../coroutines.py:75>>]
Number: 0
Number: 1
Number: 2
Number: 3
Number: 4

到这里,其实我们已经可以使用协程了,但是好像并没有和正常的方式有什么出入,甚至还更麻烦了,那么协程到底在什么场景下使用呢?

协程在IO密集型任务方面有着很大的有优势,一般包括文件读写,网络请求等。下面我们将在爬虫(网络请求)的场景下继续介绍协程。

详见【网络爬虫实战】异步爬虫

分类: Python

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注