Python高级编程笔记 (线程/进程/协程)
1.线程
使用Threading模块中的Thread类来创建线程,如需传递执行的函数的参数,通过Thread的args参数传递即可.
import threading
import time
def worker():
print(f"Thread {threading.current_thread().name} is starting")
time.sleep(2)
print(f"Thread {threading.current_thread().name} is finishing")
# 创建线程
threads = []
for i in range(5):
t = threading.Thread(target=worker, name=f"Worker-{i}")
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print("All threads have finished")
使用类定义线程 :通过继承 threading.Thread 类来定义线程 .setDaemon 设置守护线程
import time
from threading import Thread
class MyThread(Thread):
def __init__(self, name: str, count: int):
super().__init__()
self.setName(name)
self.setDaemon(True)
self.count = count
def run(self) -> None:
for n in range(self.count):
print(f"{self.getName()} - {n}\n", end='')
time.sleep(0.01)
# 创建并启动线程
threads = []
for i in range(1, 5):
t = MyThread("Thread-" + str(i), i * 5)
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print("All threads have finished")
线程安全队列
queue模块中的Queue类提供了线程安全队列功能
queue.put(item, block=False)
queue.put(item, timeout=3)
queue.get(block=False)
queue.get(timeout=10)
queue.qsize()
queue.empty()
queue.full()
from threading import Thread
from queue import Queue
class MsgProducer(Thread):
def __init__(self, name: str, count: int, queue: Queue):
super().__init__()
self.setName(name)
self.count = count
self.queue = queue
def run(self) -> None:
for n in range(self.count):
msg = f"Msg come from {self.getName()} - {n}"
self.queue.put(msg, block=True)
class MsgConsumer(Thread):
def __init__(self, name: str, queue: Queue):
super().__init__()
self.setName(name)
self.queue = queue
self.setDaemon(True)
def run(self) -> None:
while True:
msg = self.queue.get(block=True)
print(f"{self.getName()} - {msg}\n", end='')
queue = Queue(3)
threads = list()
threads.append(MsgProducer("PA", 10, queue))
threads.append(MsgProducer("PB", 10, queue))
threads.append(MsgProducer("PC", 10, queue))
threads.append(MsgConsumer("CA", queue))
threads.append(MsgConsumer("CB", queue))
for t in threads:
t.start()
线程锁
01.使用threading.Lock 对象实现。 主要方法有
from threading import Thread, Lock, Condition task_lock = Lock() #创建锁 task_lock.acquire() #获取锁 task_lock.release() #释放锁
import threading # 创建一个锁对象 lock = threading.Lock() # 共享资源:计数器 counter = 0 def increment(): global counter for _ in range(10000): # 获取锁 lock.acquire() try: counter += 1 finally: # 释放锁 lock.release() def decrement(): global counter for _ in range(10000): # 获取锁 lock.acquire() try: counter -= 1 finally: # 释放锁 lock.release() # 创建两个线程,分别执行increment和decrement函数 threads = [ threading.Thread(target=increment), threading.Thread(target=decrement) ] # 启动所有线程 for thread in threads: thread.start() # 等待所有线程完成 for thread in threads: thread.join() print(f'Final counter value: {counter}')
02. 使用threading.Condition实现线程锁
使用场景:
当你需要在多个线程之间进行更复杂的同步操作时,可以使用 Condition。
例如,在生产者-消费者模式中,生产者线程需要等待消费者线程处理完数据后才能继续生产新的数据;而消费者线程需要等待生产者线程生产出数据后才能进行消费。这种情况下可以使用 Condition 来实现线程间的通信和同步。
使用方法:
创建一个条件变量对象:condition = threading.Condition()
在需要保护的代码段之前调用 condition.acquire() 获取锁。
在满足特定条件时调用 condition.notify() 或 condition.notify_all() 唤醒等待的线程。
在需要等待特定条件时调用 condition.wait() 进入等待状态。
在代码段执行完成后调用 condition.release() 释放锁
2.线程池
concurrent.futures.ThreadPoolExecutor : submit() map() shutdown()
Future : result() exception()
import os.path import time from concurrent.futures import ThreadPoolExecutor def task(name: str): print(f"{name} - step 1\n", end='') time.sleep(1) print(f"{name} - step 2\n", end='') return f"{name} complete" # 示例1 使用submit 创建线程池 with ThreadPoolExecutor() as executor: result_1 = executor.submit(task, 'A') result_2 = executor.submit(task, 'B') print(result_1.result()) print(result_2.result()) # 示例2 使用map 创建多个线程,仅task参数不同 with ThreadPoolExecutor() as executor: results = executor.map(task, ['C', 'D']) for r in results: print(r)
3.多进程
Python提供了multiprocessing模块来支持多进程
multiprocessing.Process用于创建进程
Process类
start() 用于启动进程
join()用于等待进程结束
import multiprocessing import time # 定义一个函数作为进程的任务 def worker(name): print(f'Worker {name} started') time.sleep(2) # 模拟耗时操作 print(f'Worker {name} finished') if __name__ == '__main__': # 创建进程列表 processes = [] # 创建并启动多个进程 for i in range(5): p = multiprocessing.Process(target=worker, args=(f'Process-{i}',)) processes.append(p) p.start() # 等待所有进程完成 for p in processes: p.join() print('All processes have completed')
4.进程池
可以使用 multiprocessing 模块中的 Pool 类来创建进程池。
import multiprocessing import time # 定义一个函数作为进程的任务 def worker(num): print(f'Worker {num} started') time.sleep(2) # 模拟耗时操作 print(f'Worker {num} finished') return num * num if __name__ == '__main__': # 创建进程池,指定进程数量为4 with multiprocessing.Pool(processes=4) as pool: # 使用map方法将任务分配给进程池中的进程 results = pool.map(worker, range(10)) # 打印结果 print('Results:', results)
5.协程
asyncio模块通过一个线程执行并发任务 , 通过async和await关键字提供支持 ,通过事件循环来实现,使用async 关键字来定义函数是一个协程。
**定义与资源隔离**:
- **协程**:协程是一种用户级的轻量级线程,它的执行控制完全由程序员自己决定。协程通过 `async` 和 `await` 关键字来实现,可以在需要的时候暂停和恢复执行。协程的切换开销较小,因为不需要涉及操作系统内核的调度。
**创建与销毁成本**:
- **协程**:协程的内存开销非常小,只需要一个栈帧即可。因此可以创建大量的协程,适用于处理高并发场景。
**并发性与CPU利用率**:
- **协程**:协程适合处理I/O密集型任务,在遇到I/O操作时可以通过 `await` 暂停当前任务并立即切换到其他任务,从而充分利用等待时间来执行其他任务。但由于受到GIL的限制,协程在CPU密集型任务中无法真正实现并行计算。
**通信与同步**:
- **协程**:协程之间的通信和同步相对简单,可以通过事件循环和异步IO库(如 `asyncio`)来实现。
**异常处理与稳定性**:
- **协程**:如果一个协程崩溃,不会影响其他协程,因为每个协程都是独立的执行单元。
### 总结
- **线程**:适用于I/O密集型任务和简单的并发操作,例如网络请求、文件读写等。在这种情况下,线程可以有效地利用等待时间来执行其他任务。
- **进程**:适用于CPU密集型任务和需要更高稳定性的场景,例如科学计算、大数据处理等。在这种情况下,进程可以充分利用多核CPU的计算能力,并且具有更高的稳定性。
- **协程**:适用于需要更高效率的并发场景,例如异步IO、事件驱动编程等。在这种情况下,协程可以充分利用多核CPU的计算能力,并且具有更高的效率和更低的资源消耗。
选择使用线程、进程还是协程,需要根据具体的应用场景和需求来决定。如果任务是I/O密集型的,可以优先考虑使用线程或协程;如果任务是CPU密集型的或需要更高的效率,可以优先考虑使用进程。
import asyncio import time async def cal(n1, n2): result = n1 + n2 await asyncio.sleep(1) # 模拟耗时操作 return result async def main(): time_1 = time.perf_counter() print("main is starting") # 创建多个任务 task_1 = asyncio.create_task(cal(1, 2)) task_2 = asyncio.create_task(cal(3, 4)) # 使用 asyncio.gather 同时等待多个任务完成,并获取返回值 results = await asyncio.gather(task_1, task_2) # 打印结果 print(f"Results: {results}") print("Both tasks are completed") time_2 = time.perf_counter() print(f"main is finishing in {time_2 - time_1} seconds") # 运行主协程 asyncio.run(main()) # Output: # main is starting # Results: [3, 7] # Both tasks are completed # main is finishing in 1.0017698 seconds
import asyncio from asyncio.exceptions import TimeoutError async def play_music(music: str): print(f"Start playing {music}") await asyncio.sleep(3) print(f"Finished playing {music}") return music async def call_api(): print("calling api.....") raise Exception("Error calling") async def my_cancel(): task = asyncio.create_task(play_music("A")) await asyncio.sleep(3) if not task.done(): task.cancel() async def my_cancel_with_timeout(): task = asyncio.create_task(play_music("B")) try: await asyncio.wait_for(task, timeout=2) except TimeoutError: print("timeout") async def my_timeout(): task = asyncio.create_task(play_music("B")) try: await asyncio.wait_for(asyncio.shield(task), timeout=2) except TimeoutError: print("timeout") await task async def my_gather(): results = await asyncio.gather(play_music("A"), play_music("B")) print(results) async def my_gather_with_exception(): results = await asyncio.gather(play_music("A"), play_music("B"), call_api(), return_exceptions=True) print(results) if __name__ == "__main__": asyncio.run(my_gather_with_exception())