Python中并发方面的差异
人们可能会想象Python才刚刚引入这些概念或能力,因为随着Python 3的发布,我们听到了很多关于异步操作和并发的新趋势。
许多新手可能认为使用asyncio
是执行并发和异步活动的唯一实用方法。本文将讨论我们如何在Python中实现并发及其好处或坏处。
线程和多线程
线程在Python中已经存在很长时间了。因此,由于线程的存在,我们可以同时执行多个操作。
不幸的是,CPython
,一个典型的Python主线版本,仍然使用全局解释器锁 (GIL
),这使得多线程应用–现今常见的实现并行处理的方法–不是很理想。
Python引入了GIL
,以使CPython的内存处理更易于管理与C的集成(例如,扩展)。
GIL
是一种锁定机制,即Python解释器只能同时运行一个线程。Python的byte
代码永远只能由一个线程同时执行。
示例代码:
import threading
import time
import random
def worker(num):
sec = random.randrange(1, 5)
time.sleep(sec)
print("I am thread {}, who slept for {} seconds.".format(num, sec))
for i in range(3):
t = threading.Thread(target=worker, args=(i,))
t.start()
print("Completed!")
输出:
Completed!
I am thread 1, who slept for 3 seconds.
I am thread 3, who slept for 2 seconds.
I am thread 4, who slept for 4 seconds.
进程和多进程
多进程利用了许多CPU。我们可以有效地同时进行几个任务,因为每个CPU都是并行运行的。对于那些受CPU约束的工作,多处理是你想使用的。
Python引入了multiprocessing
模块来实现并行化,如果你使用过线程,会感觉非常相似。
示例代码:
import multiprocessing
import time
import random
def worker(num):
sec = random.randrange(1, 5)
time.sleep(sec)
print("I am process {}, who slept for {} seconds.".format(num, sec))
for i in range(3):
t = multiprocessing.Process(target=worker, args=(i,))
t.start()
print("Completed")
输出:
Completed
I am process 1, who slept for 1 seconds.
I am process 2, who slept for 2 seconds.
I am process 0, who slept for 3 seconds.
与其说是多线程,不如说是在你的CPU的不同核心上运行多个进程,这使我们的Python脚本更快。
异步和asyncio
在同步操作中,任务是同步进行的,一个接一个。然而,在异步操作中,任务的开始可能完全是相互独立的。
一个async
任务可能开始并继续运行,而执行则切换到另一个活动。另一方面,异步任务经常在后台执行,不会阻塞(让执行者等待完成)。
除了其他有价值的功能外,asyncio
还提供了一个事件循环。事件循环监控各种I/O事件,切换到准备好的任务,并暂停等待I/O的任务。
因此,我们不会将时间浪费在未完成的项目上。
示例代码:
import asyncio
import datetime
import random
async def my_sleep_func():
await asyncio.sleep(random.randint(0, 5))
async def displayDate(num, loop):
endTime = loop.time() + 60.0
while True:
print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= endTime:
break
await my_sleep_func()
loop = asyncio.get_event_loop()
asyncio.ensure_future(displayDate(1, loop))
asyncio.ensure_future(displayDate(2, loop))
loop.run_forever()
如果我们走过上面的代码片断:
- 我们有一个
async
函数displayDate
,它接受一个数字和事件循环作为参数。 - 上述函数有一个无限循环,60秒后停止。但在这60秒内,它重复地打印出时间并打盹。
-
await
函数可以等待其他async
函数完成。 - 我们将该函数传递给事件循环(使用
ensure_future
函数)。 - 我们开始运行事件循环。
每当调用await
,asyncio
明白该函数可能需要一些时间。当asyncio
注意到停止的函数的I/O已经准备好了,它就恢复进程。
现在,问题是,在这三种形式的并发中,我们需要使用什么?我们可以注意到以下几点来帮助我们决策:
- 对CPU绑定的操作使用多处理。
- 对I/O Bound、快速I/O和有限的连接数使用多线程。
- 对于I/O绑定、慢速I/O和许多连接,使用异步IO。
-
asyncio/await
在Python 3.5和更高版本上工作。
我们也可以参考下面的伪代码:
if io_bound:
if io_very_slow:
print("Use asyncio")
else:
print("Use multithreading")
else:
print("multiprocessing")