python线程、进程和协程详解
Python 线程、进程和协程详解
在 Python 中,程序运行的实体可以分为线程、进程和协程。它们各自有着不同的特点和适用范围。
线程
什么是线程?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中真正执行的实体。
Python 的线程是操作系统的原生线程,由操作系统调度。Python 使用 threading 模块来创建线程。
如何创建线程?
可以使用 threading.Thread 类创建线程,例如:
import threading
def task():
print("This is a task.")
t = threading.Thread(target=task)
t.start()
上述代码使用 threading.Thread 类创建了一个新的线程 t,并把 task 函数作为线程的执行目标。t.start() 表示启动线程。
线程的状态
线程的状态包括:
- 新建状态(
NEW):线程对象已经创建,但是还没有调用start()方法。 - 就绪状态(
READY):线程对象已经调用start()方法,等待系统分配线程资源。 - 运行状态(
RUNNING):线程处于可执行状态并且正在运行中。 - 阻塞状态(
BLOCKED):线程因为某些原因暂停了自己的执行,例如等待 I/O 操作完成。 - 终止状态(
TERMINATED):线程执行完成,退出了。
线程同步
多个线程有时需要协调它们的执行顺序,避免出现冲突和不一致的结果。这就需要线程同步。Python 提供了多种线程同步工具,例如 Lock、RLock、Semaphore 等。
以下是一个使用 Lock 实现线程同步的示例:
import threading
cnt = 0
lock = threading.Lock()
def increase():
global cnt
lock.acquire()
cnt += 1
lock.release()
threads = []
for i in range(10):
t = threading.Thread(target=increase)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
print(cnt)
上述代码定义了一个共享变量 cnt,并使用 Lock 同步多个线程对 cnt 的修改。如果不加同步,可能会出现多个线程同时修改 cnt,导致结果出错。加上同步之后,每次只有一个线程能够获得锁,保证了对 cnt 同时只有一个线程在修改。
进程
什么是进程?
进程是操作系统中执行的一个程序。在 Unix 和 Linux 系统中,每个进程都有一个唯一的进程标识符(PID)。
Python 使用 multiprocessing 模块来创建进程。
如何创建进程?
可以使用 multiprocessing.Process 类创建进程,例如:
import multiprocessing
def task():
print("This is a task.")
p = multiprocessing.Process(target=task)
p.start()
上述代码使用 multiprocessing.Process 类创建了一个新的进程 p,并把 task 函数作为进程的执行目标。p.start() 表示启动进程。
进程间通信
不同进程之间无法直接共享内存,需要使用进程间通信(IPC)的方式来交换数据和消息。Python 提供了多种进程间通信方式,例如 Queue、Pipe、Manager 等。
以下是一个使用 Queue 实现进程间通信的示例:
import multiprocessing
def worker(q):
while True:
item = q.get()
if item is None:
break
print(item)
q = multiprocessing.Queue()
p = multiprocessing.Process(target=worker, args=(q,))
p.start()
for i in range(10):
q.put(i)
q.put(None)
p.join()
上述代码定义了一个 worker 函数来处理 Queue 中的数据,主进程向 Queue 中写入 10 个整数并以 None 结束。worker 进程不断从 Queue 中取出数据并打印,直到碰到 None 为止。
进程池
进程池是一种并发编程的方式,它可以在执行任务时,自动帮助开发者维护一定数目的进程数量,在某些场景下可以提升程序的执行效率。
Python 提供了 multiprocessing.Pool 模块来实现进程池。
以下是一个使用进程池实现多进程并发下载的示例:
import requests
from multiprocessing.pool import Pool
urls = [
"https://www.python.org",
"https://www.github.com",
"https://www.baidu.com",
"https://www.bing.com",
]
def download(url):
resp = requests.get(url)
print(url, len(resp.content))
pool = Pool(processes=4)
pool.map(download, urls)
pool.close()
pool.join()
上述代码定义了一个 download 函数来下载指定 URL 的内容,并使用进程池同时下载多个 URL。processes=4 表示创建一个拥有 4 个进程的进程池。map 方法接受一个列表作为参数,表示要执行的任务列表,自动分配给进程池中的进程进行执行。
协程
什么是协程?
协程是一种用户态(用户级)的线程,不需要操作系统进行线程调度,可以由应用程序主动挂起和恢复。与进程、线程相比,协程更加轻量级,能够同时执行许多任务。
Python 使用 asyncio 模块来支持协程。
如何创建协程?
可以使用 async def 关键字定义协程函数,例如:
import asyncio
async def task():
print("This is a coroutine task.")
loop = asyncio.get_event_loop()
loop.run_until_complete(task())
loop.close()
上述代码定义了一个 task 协程函数,并使用 loop.run_until_complete 方法来运行它。
协程间通信
与线程类似,协程之间也需要通信来协调它们之间的执行。Python 提供了 asyncio.Queue 类来实现协程间的通信。
以下是一个使用 asyncio.Queue 实现协程间通信的示例:
import asyncio
async def producer(q):
for i in range(10):
await q.put(i)
await q.put(None)
async def consumer(q):
while True:
item = await q.get()
if item is None:
break
print(item)
q = asyncio.Queue()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
producer(q),
consumer(q),
))
loop.close()
上述代码定义了一个 producer 协程函数和一个 consumer 协程函数,producer 协程向 Queue 中写入 10 个整数并以 None 结束,consumer 协程不断从 Queue 中取出数据并打印,直到碰到 None 为止。
协程池
协程池是一种更加高级的协程管理方式,其可以同时管理多个协程,根据任务情况决定是否创建新协程,提升协程的效率。
Python 提供了 asyncio 模块的 ThreadPoolExecutor 和 ProcessPoolExecutor 类来实现协程池。
以下是一个使用协程池同时下载多个 URL 的示例:
import asyncio
import aiohttp
urls = [
"https://www.python.org",
"https://www.github.com",
"https://www.baidu.com",
"https://www.bing.com",
]
async def download(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(url, len(await resp.read()))
async def main():
tasks = [asyncio.ensure_future(download(url)) for url in urls]
await asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
上述代码定义了一个 download 协程函数,使用 aiohttp 库下载指定 URL 的内容。通过创建多个 asyncio.ensure_future 对象并使用 asyncio.gather 方法同时运行多个协程。线程池和进程池的实现与之类似,只需要将 ThreadPoolExecutor 或 ProcessPoolExecutor 作为事件循环的参数即可。
总的来说,Python 中的线程、进程、协程的使用方式多种多样,开发者可以根据自己的业务需求来选择适合的方式来实现多任务并发。
