python 进程、线程、协程详解

1. 线程

在介绍Python线程之前,我们需要先了解什么是线程。线程是操作系统能够切换和调度的最小单位,它被包含在进程中,是进程中的实际运作单位。Python的线程模块被设计为与该语言的GIL(全局解释器锁)协同工作,也就是说多个线程不能同时执行同一份Python代码。

GIL:是Python解释器实现的一个全局锁,它确保同一时间只有一个线程可以执行Python字节码。这也就意味着在Python中, 多线程并不能真正意义上并行执行代码。

1.1 线程创建与启动

Python中可以通过继承Thread类或传入target函数来创建线程,例如:

import threading

class MyThread(threading.Thread):

def __init__(self, args):

threading.Thread.__init__(self)

self.args = args

def run(self):

# 任务

pass

my_thread = MyThread(args)

my_thread.start()

其中start()方法启动线程,run()方法表示线程要执行的任务。

也可以使用threading模块的Thread类直接创建线程:

import threading

def func(args):

# 任务

pass

my_thread = threading.Thread(target=func, args=(args,))

my_thread.start()

1.2 线程同步与互斥

在多线程编程中,经常会出现多个线程共享同一个数据区域的情况,这就会带来线程同步和互斥的问题。

线程同步:是指在多个线程中协调彼此的工作,以便执行顺序符合预期。例如,在两个线程中,如果某个操作必须在第一个线程完成之后方可进行,那么就需要通过同步机制实现。

线程互斥:意味着只有一个线程能够访问某个数据区域或某个设备。互斥锁是实现线程互斥的一种常用机制。

Python中可以使用threading模块的Lock类来实现线程同步和互斥:

import threading

lock = threading.Lock()

def func():

lock.acquire()

# 操作

lock.release()

在线程需要访问共享资源时,首先需要使用acquire()方法获取锁,表示这个线程正在使用这个资源。当线程使用完资源之后,需要使用release()方法释放锁,以便其他线程可以使用。

1.3 线程常用API

Python的threading模块提供了多个常用的API:

active_count():返回当前活动线程的数量。

current_thread():返回当前线程实例。

enumerate():返回当前活动线程的列表。

is_alive():判断线程是否在活动状态。

join([timeout]):阻塞当前线程,直到调用join()方法的线程执行完成。可选的timeout参数表示最长的阻塞时间。

下面是一个使用join()方法的案例:

import threading

def func():

# 操作

pass

threads = []

for i in range(5):

t = threading.Thread(target=func)

threads.append(t)

for t in threads:

t.start()

for t in threads:

t.join()

在这个案例中,我们创建了5个线程并启动了它们。使用join()方法将主线程阻塞,直到所有线程执行完成。

2. 进程

在操作系统中,进程是指正在运行的程序的一次执行过程,是系统进行资源分配和调度的基本单位。进程由一个或多个线程组成,每个线程都可以完成一定的任务。

Python中可以使用multiprocessing模块来创建和管理进程。与线程不同,Python中的每个进程都有自己独立的解释器和全局解释器锁。

2.1 进程创建与启动

在Python中,我们可以通过Process类来创建和管理进程:

import multiprocessing

def func(args):

# 操作

pass

my_process = multiprocessing.Process(target=func, args=(args,))

my_process.start()

Process对象实例化时需要传入要执行的函数和该函数的参数。start()方法则启动新的进程。

2.2 进程同步与共享

与线程类似,进程中也会出现多个进程共享同一个数据区域的情况,这就会涉及进程同步和共享的问题。Python中可以使用multiprocessing模块的Lock和Value类来实现进程同步和共享:

import multiprocessing

lock = multiprocessing.Lock()

val = multiprocessing.Value('i', 0)

def func():

with lock:

val.value += 1

使用with语句可以确保在进程使用完共享资源之后,自动释放锁,以便其他进程可以使用。Value类用于共享数值,第一个参数指定要共享的变量类型,第二个参数指定初始值。

2.3 进程间通信

在Python中,进程之间可以通过queue模块来进行通信。queue模块是Python标准库中的多线程模块与多进程模块共有的模块之一,提供了一个队列类,可以安全地在多个进程中共享数据。

下面是一个queue模块的案例:

import multiprocessing

def producer(q):

# 生产者

q.put(item)

def consumer(q):

# 消费者

item = q.get()

if __name__ == '__main__':

q = multiprocessing.Queue()

p1 = multiprocessing.Process(target=producer, args=(q,))

p2 = multiprocessing.Process(target=consumer, args=(q,))

p1.start()

p2.start()

p1.join()

p2.join()

在这个案例中,我们使用Queue类来创建一个队列,然后创建两个进程,一个用作生产者,一个用作消费者。由于在Windows中创建新进程时会重新运行脚本,因此需要在if __name__ == '__main__'语句中调用queue模块,以避免无限递归。

3. 协程

协程是一种轻量级的线程,也称作微线程或纤程,可以理解为用户态线程。协程与线程相比,更加轻量级,可以轻松地创建和销毁,而且切换代价很小,切换速度不会受到操作系统的限制。

Python中可以使用asyncio库来实现协程。asyncio库是Python 3.4版本后引入的标准库,它提供了一整套协程相关的API和工具,尤其适用于I/O密集型操作。

3.1 协程创建与启动

在Python中,可以使用async关键字来定义协程。协程的语法与普通函数类似,不同的是在函数定义中添加async关键字。

import asyncio

async def coro():

# 协程

pass

loop = asyncio.get_event_loop()

loop.run_until_complete(coro())

在这个案例中,我们使用asyncio库的get_event_loop()方法创建一个事件循环对象,然后使用run_until_complete()方法运行协程。

3.2 协程同步

协程中的异步操作一般会涉及回调函数,而回调函数又会涉及到协程中的异步操作,这样就会形成一种无限嵌套的结构。为了解决这个问题,Python 3.5版本引入了async/await关键字,使得协程编程更加简单和直观。

下面是一个使用async/await关键字的案例:

import asyncio

async def coro():

# 异步操作

result = await asyncio.sleep(1)

# 操作结果

pass

loop = asyncio.get_event_loop()

loop.run_until_complete(coro())

在这个案例中,我们使用asyncio库的sleep()方法模拟一个异步操作,并使用await关键字暂停协程,等待异步操作结束。一旦异步操作完成,协程就会被恢复,并继续执行下去。

3.3 事件循环

在协程编程中,事件循环是一个非常重要的概念。事件循环是一种循环机制,用于等待和处理事件。在Python中,可以使用asyncio库的事件循环来管理协程。

下面是一个使用asyncio事件循环的案例:

import asyncio

async def coro():

# 异步操作

pass

async def main():

task1 = asyncio.create_task(coro())

task2 = asyncio.create_task(coro())

await asyncio.gather(task1, task2)

asyncio.run(main())

在这个案例中,我们使用create_task()方法创建了两个协程任务,然后使用gather()方法等待这两个任务完成。最后使用run()方法启动一个事件循环,开始执行协程。

后端开发标签