python基础10 多线程、多进程和协程

第九章节的日志部分,已经涉及到了多线程和多进程的概念,所以在此章节单独做一个总结。

主要内容参考:

Python 多线程与多进程详解:高效并发编程实践-CSDN博客

链接中的文章已经讲的比较清晰,作者转载到此供自我学习使用。

基础知识

在 Python 开发中,并发编程 是提高程序执行效率的重要手段。对于 I/O 密集型任务,如文件处理、网络爬虫、数据库操作,可以使用多线程(threading) 或 异步编程(asyncio)。对于 CPU 密集型任务,可以使用多进程(multiprocessing),充分利用多核 CPU;

一、线程 vs 进程

特性线程(Thread)进程(Process)
适用场景I/O 密集型任务(如爬虫、文件 I/O)CPU 密集型任务(如科学计算、图像处理)
Python GIL 限制受限(无法利用多核 CPU)不受限(可以利用多核 CPU)
内存共享共享内存(开销小)独立内存(开销大)
启动速度
数据同步需要锁(Lock)无需锁

 结论

  • I/O 密集型任务(爬虫、网络请求):推荐使用多线程 或 协程(asyncio)
  • CPU 密集型任务(科学计算、数据处理):推荐使用多进程(multiprocessing)--需要使用多核CPU场景。

二、Python 多线程(Threading)

2.1 线程的基本使用

import threading
import time

def task(name):
    print(f"线程 {name} 开始执行")
    time.sleep(2)
    print(f"线程 {name} 结束")

# 创建线程
thread1 = threading.Thread(target=task, args=("A",))
thread2 = threading.Thread(target=task, args=("B",))

# 启动线程
thread1.start()
thread2.start()

# 等待所有线程完成
thread1.join()
thread2.join()

print("所有线程执行完毕")

输出:

线程 A 开始执行
线程 B 开始执行
线程 A 结束
线程 B 结束
所有线程执行完毕

解释

  • Thread(target=task, args=(...)) 创建新线程

  • start() 启动线程

  • join() 等待线程执行完毕


2.2 线程池 ThreadPoolExecutor

对于大量任务,使用 ThreadPoolExecutor 管理线程池比手动创建 Thread 更高效:

import concurrent.futures
import time

def task(name):
    print(f"线程 {name} 开始")
    time.sleep(2)
    print(f"线程 {name} 结束")
    
# 使用线程池
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    executor.map(task, ["A", "B", "C", "D"])

优势

  • 自动管理线程,避免手动创建 Thread
  • 适用于并发 I/O 任务,如爬虫、数据库查询

2.3 线程同步

Lock 互斥锁

多个线程同时修改同一个变量时,可能发生 竞态条件(Race Condition),导致数据不一致:

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        with lock:  # 使用锁防止数据竞争
            counter += 1

threads = [threading.Thread(target=increment) for _ in range(3)]
for t in threads: t.start()
for t in threads: t.join()

print("最终计数值:", counter)

为什么需要锁(Lock)?

  • Python 的 GIL(全局解释器锁) 允许多个线程执行,但 不能同时修改共享变量
  • with lock: 确保一次只有一个线程修改 counter

 消息队列

消息队列是一种更安全的线程通信方式。Python 的 queue 模块提供了线程安全的队列,可以在多个线程之间安全地交换数据。

import threading
from queue import Queue
import time
from concurrent.futures import ThreadPoolExecutor
 
def get_detail_html(detail_url_list, id):
    while True:
        url = detail_url_list.get()
        #设置退出机制
        if url is None:
            break
        time.sleep(2)
        print(f"thread {id}: get {url} detail finished")
    print(f"html thread {id} end")
 
def get_detail_url(queue):
    for i in range(10):
        time.sleep(1)
        queue.put(f"url: {i}")
        print(f"get detail url {i} end")
    #设置退出机制 不然线程内部queue读取循环无法结束 
    #写入数量>=读取线程数
    for i in range(10):
        queue.put(None)    
    print("url thread end")

 
if __name__ == "__main__":
    detail_url_queue = Queue(maxsize=1000)
    start_time = time.time()

    #独立thread模式
    '''
    html_thread = []
    for i in range(3):
        thread2 = threading.Thread(target=get_detail_html, args=(detail_url_queue, i))
        html_thread.append(thread2)

    #启动所有queue的读取线程    
    for i in range(3):
        html_thread[i].start()
      
    '''
    # 线程池模式 修改重点!!!!!!!
    future_list = []
    executor = ThreadPoolExecutor(max_workers=5)
    for i in range(5):
        future =  executor.submit(get_detail_html, detail_url_queue,i)
        future_list.append(future) 
 

   #启动queue的写入线程 
    thread = threading.Thread(target=get_detail_url, args=(detail_url_queue,))
    thread.start()

    #等待所有线程结束 
    thread.join()   

    for f in future_list:
        f.result()
    executor.shutdown()

    #独立thread模式
    '''
    for i in range(3):
        html_thread[i].join()
    ''' 

    print(f"last time: {time.time() - start_time} s")

三、Python 多进程(Multiprocessing)

3.1 多进程的基本使用

import multiprocessing
import os

def worker(name):
    print(f"进程 {name} (PID={os.getpid()}) 开始执行")
    return name

if __name__ == "__main__":
    process1 = multiprocessing.Process(target=worker, args=("A",))
    process2 = multiprocessing.Process(target=worker, args=("B",))

    process1.start()
    process2.start()

    process1.join()
    process2.join()
    
    print("所有进程执行完毕")

优势

  • 每个进程独立运行,不受 GIL 限制
  • 适用于 CPU 密集型任务,如大规模计算、图像处理

3.2 进程池 ProcessPoolExecutor

import concurrent.futures
import os

def task(name):
    print(f"进程 {name} (PID={os.getpid()}) 开始执行")
    return name

if __name__ == "__main__":
    with concurrent.futures.ProcessPoolExecutor(max_workers=3) as executor:
        results = executor.map(task, ["A", "B", "C", "D"])

适用场景

  • CPU 密集型任务
  • 提高计算任务的并行性

3.3 进程间通信

3.3.1 共享内存

使用 multiprocessing.Value 可以创建进程间共享的变量,下面是一个例子,创建了一个类型为整数(‘i’)的共享内存变量 value

import multiprocessing
def func(value):
    print("read value %d" % value.value)

if __name__ == '__main__':
    value = multiprocessing.Value('i', 1000)
    processes = [multiprocessing.Process(target=func, args=(value,)) for _ in range(10)]
    for process in processes:
        process.start()
    for process in processes:
        process.join()
    print(value.value) # 输出 10

 3.3.2 管道

使用 multiprocessing.Pipe 可以创建一个管道,两个进程可以通过这个管道互相传递数据,下面是一个例子,创建了一个管道,其中 parent_conn 是父进程持有的端口,child_conn 是子进程持有的端口。然后启动两个进程,分别调用 sender 和 receiver 函数。sender 函数发送一条消息到管道中,receiver 函数从管道中接收消息并打印出来:

import multiprocessing
def sender(conn):
    conn.send('Hello, receiver')
def receiver(conn):
    message = conn.recv()
    print(message)
if __name__ == '__main__':
    parent_conn, child_conn = multiprocessing.Pipe()
    p1 = multiprocessing.Process(target=sender, args=(parent_conn,))
    p2 = multiprocessing.Process(target=receiver, args=(child_conn,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

3.3.3 通过 Queue 共享数据

import multiprocessing

def worker(queue):
    queue.put("任务完成")

if __name__ == "__main__":
    q = multiprocessing.Queue()
    process = multiprocessing.Process(target=worker, args=(q,))
    process.start()
    process.join()

    print("消息:", q.get())  # 读取子进程发送的消息

四、协程(AsyncIO)适用于 I/O 密集型任务

对于 网络请求、文件 I/O、数据库操作asyncio 提供更高效的异步处理。

4.1 asyncio 基本使用

import asyncio

async def async_task(name):
    print(f"任务 {name} 开始")
    await asyncio.sleep(2)  # 模拟异步 I/O
    print(f"任务 {name} 结束")

async def main():
    tasks = [async_task("A"), async_task("B")]
    await asyncio.gather(*tasks)

asyncio.run(main())

优势

  • 非阻塞执行
  • 适用于网络爬虫、API 请求、数据库操作

五、总结

方案适用场景主要特性
多线程(threading)I/O 密集型(爬虫、文件 I/O)共享内存,受 GIL 限制
线程池(ThreadPoolExecutor)并发 I/O 任务自动管理线程
多进程(multiprocessing)CPU 密集型任务多核计算,不受 GIL 限制
进程池(ProcessPoolExecutor)大规模计算任务自动管理进程
协程(asyncio)I/O 高并发(API、爬虫)非阻塞执行
如何选择?
  1. CPU 密集型任务(科学计算、数据分析) → 多进程
  2. I/O 密集型任务(爬虫、数据库查询) → 多线程 或 asyncio
  3. 高并发任务(网络请求、日志处理) → asyncio
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值