Python 支持多进程,多线程,以及协程,但是有 GIL 全局锁的限制,Python 只能运行一个系统级线程,无法利用多核 CPU。所以 Python 中的多线程,协程只适合 IO 密集型任务,而不适合 CPU 密集型任务。在需要执行 CPU 密集型任务是推荐采用多进程,或是使用 C 语言扩展。

多进程

Python的``multiprocessing`模块提供了多进程接口。实例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import multiprocessing, time

def countdown(n):
    while n > 0:
        print('T-minus', n)
        n -= 1
        time.sleep(3)

if __name__ == "__main__":
    #Creat and lanch a process, must be in __main__
    p = multiprocessing.Process(target=countdown, args=(10,))
    p.start()

多线程

Pythonthreading模块提供了多线程接口,接口与多进程类似。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import time
def countdown(n):
    while n > 0:
        print('T-minus', n)
        n -= 1
        time.sleep(3)

# Create and launch a thread
from threading import Thread
t = Thread(target=countdown, args=(10,))
t.start()

协程

协程可以说是轻量级线程,但是协程可以在运行到一半时暂时挂起,转而去执行其他的程序,再在适当的适合返回继续执行。

协程实现消费者,生产者模型(参考廖雪峰教程)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

这里的consumer函数是一个generator,把一个consumer传入produce后:

  1. 首先调用c.send(None)启动生成器;

  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;

  4. produce拿到consumer处理的结果,继续生产下一条消息;

  5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produceconsumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

asycnio

asyncio是 Python 3.4 版本引入的标准库,直接内置了对异步 IO 的支持

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步 IO

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import asyncio

async def hello():
    print("Hello world!")
    # 异步调用 asyncio.sleep(1):
    r = await asyncio.sleep(1)
    print("Hello again!")

# 获取 EventLoop:
loop = asyncio.get_event_loop()
# 执行 coroutine
loop.run_until_complete(hello())
loop.close()

aiohttp

aiohttp是基于asyncio实现的 HTTP 框架

利用aiohttp异步抓取网页:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import asyncio
import aiohttp

headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64;\
 x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}

async def get_html(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as resp:
            print(await resp.text())

loop = asyncio.get_event_loop()
tasks = [get_html(url) for url in ['http://www.sina.com.cn', 'http://www.sohu.com', 'http://www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

aiomysql

aiomyql是基于asyncio实现的异步操作MySQL数据库的框架

example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import asyncio
import aiomysql

loop = asyncio.get_event_loop()

@asyncio.coroutine
def test_example():
    conn = yield from aiomysql.connect(host='127.0.0.1', port=3306,
                                       user='root', password='', db='mysql',
                                       loop=loop)

    cur = yield from conn.cursor()
    yield from cur.execute("SELECT Host,User FROM user")
    print(cur.description)
    r = yield from cur.fetchall()
    print(r)
    yield from cur.close()
    conn.close()

loop.run_until_complete(test_example())