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()方法启动一个事件循环,开始执行协程。