摘要
本文用2种方式来实现python3.8的多线程逻辑。分别是线程池和多进程模块。
简单认识2种方式的特点
1.线程池ThreadPoolExecutor
-
全局解释器锁GIL是Python解释器中的一个互斥锁,确保任何时候只有一个线程在执行Python字节码。这使得多线程在CPU密集型任务中无法显著提高性能。
-
主要适用于I/O密集型任务(如文件读写、网络请求),这些任务在等待I/O操作时不会占用CPU资源。
-
所有线程共享相同的内存空间,因此内存开销较小。但共享内存可能导致线程安全问题,需要使用锁或其他同步机制来保护共享资源。
-
上下文切换相对轻量级,适合快速启动和停止线程。但在CPU密集型任务中,频繁的上下文切换可能会导致性能下降。
2.多进程multiprocessing
- 每个进程都有自己的Python解释器实例和独立的全局解释器锁GIL,因此可以绕过GIL的限制,在多核CPU上并行执行多个进程。
- 主要适用于CPU密集型任务(如数值计算、数据分析),这些任务需要大量CPU计算能力。
- 每个进程都有自己独立的内存空间,避免了线程间的数据竞争问题。但内存开销较大,因为每个进程都需要复制父进程的大部分数据结构。
- 上下文切换可以充分利用多核CPU,提高CPU利用率。但上下文切换相对较重,不适合频繁启动和停止进程。
线程池方式
# -*- coding: utf-8 -*-
import concurrent.futures
from queue import PriorityQueue
import threading
import logging
from time import sleep
# 创建日志器
logger = logging.getLogger('日志')
logger.setLevel(logging.INFO) # 设置全局日志级别为 INFO
# 创建控制台日志处理器
ch = logging.StreamHandler()
ch.setLevel(logging.INFO) # 设置控制台日志处理器的日志级别为 INFO
# 创建日志格式化器,并设置日志处理器的格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
# 添加日志处理器到日志器
logger.addHandler(ch)
#==================线程池方式====================
# 处理任务
shared_resource_lock = threading.Lock() #共享资源和对应的锁
processed_count1 = 0
def process_task1(data_batch):
global processed_count1
logger.info(f"当前线程:{threading.current_thread().name}, 开始处理数据:{data_batch}")
for data in data_batch:
logger.info(f"当前线程:{threading.current_thread().name}, 处理数据:{data}")
with shared_resource_lock:
processed_count1 += 1
logger.info(f"已处理数据总数:{processed_count1}")
# 使用线程池管理线程
def use_threadPoolExecutor():
# 创建优先级队列
task_queue = PriorityQueue()
# 线程数
thread_num = 5
# 往队列添加数据,每次处理5条
dataList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# 分批将数据放入优先队列中
for i in range(0, len(dataList), 5):
batch = dataList[i:i+5]
task_queue.put(batch)
# 启动多线程
with concurrent.futures.ThreadPoolExecutor(max_workers=thread_num) as executor:
futures = []
whilenot task_queue.empty():
batch = task_queue.get()
future = executor.submit(process_task1, batch)
futures.append(future)
sleep(2) #方便观察
# 等待所有任务完成
concurrent.futures.wait(futures)
if __name__ == '__main__':
use_threadPoolExecutor()
'''
输出:
2025-08-02 22:04:24,602 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 开始处理数据:[1, 2, 3, 4, 5]
2025-08-02 22:04:24,602 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:1
2025-08-02 22:04:24,602 - 日志 - INFO - 已处理数据总数:1
2025-08-02 22:04:24,602 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:2
2025-08-02 22:04:24,602 - 日志 - INFO - 已处理数据总数:2
2025-08-02 22:04:24,602 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:3
2025-08-02 22:04:24,602 - 日志 - INFO - 已处理数据总数:3
2025-08-02 22:04:24,602 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:4
2025-08-02 22:04:24,602 - 日志 - INFO - 已处理数据总数:4
2025-08-02 22:04:24,602 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:5
2025-08-02 22:04:24,602 - 日志 - INFO - 已处理数据总数:5
2025-08-02 22:04:26,615 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 开始处理数据:[6, 7, 8, 9, 10]
2025-08-02 22:04:26,615 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:6
2025-08-02 22:04:26,615 - 日志 - INFO - 已处理数据总数:6
2025-08-02 22:04:26,615 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:7
2025-08-02 22:04:26,615 - 日志 - INFO - 已处理数据总数:7
2025-08-02 22:04:26,615 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:8
2025-08-02 22:04:26,615 - 日志 - INFO - 已处理数据总数:8
2025-08-02 22:04:26,615 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:9
2025-08-02 22:04:26,615 - 日志 - INFO - 已处理数据总数:9
2025-08-02 22:04:26,615 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:10
2025-08-02 22:04:26,615 - 日志 - INFO - 已处理数据总数:10
2025-08-02 22:04:28,618 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 开始处理数据:[11]
2025-08-02 22:04:28,618 - 日志 - INFO - 当前线程:ThreadPoolExecutor-0_0, 处理数据:11
2025-08-02 22:04:28,618 - 日志 - INFO - 已处理数据总数:11
'''
多进程方式
# -*- coding: utf-8 -*-
import logging
import multiprocessing
# 创建日志器
logger = logging.getLogger('日志')
logger.setLevel(logging.INFO) # 设置全局日志级别为 INFO
# 创建控制台日志处理器
ch = logging.StreamHandler()
ch.setLevel(logging.INFO) # 设置控制台日志处理器的日志级别为 INFO
# 创建日志格式化器,并设置日志处理器的格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
# 添加日志处理器到日志器
logger.addHandler(ch)
#==================多进程方式====================
def process_task2(data_batch):
logger.info(f"当前进程:{multiprocessing.current_process().name}, 开始处理数据:{data_batch}")
for data in data_batch:
logger.info(f"当前进程:{multiprocessing.current_process().name}, 处理数据:{data}")
# 使用multiprocessing模块管理进程
def use_multiprocessing():
# 进程数
process_num = 5
# 往队列添加数据,每次处理5条
dataList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# 分批将数据放入列表中
batches = [dataList[i:i+5] for i inrange(0, len(dataList), 5)]
# 启动多进程
with multiprocessing.Pool(processes=process_num) as pool:
pool.map(process_task2, batches)
if __name__ == '__main__':
use_multiprocessing()
'''
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-2, 开始处理数据:[6, 7, 8, 9, 10]
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-1, 开始处理数据:[1, 2, 3, 4, 5]
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-2, 处理数据:6
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-2, 处理数据:7
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-1, 处理数据:1
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-3, 开始处理数据:[11]
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-2, 处理数据:8
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-1, 处理数据:2
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-2, 处理数据:9
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-2, 处理数据:10
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-1, 处理数据:3
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-3, 处理数据:11
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-1, 处理数据:4
2025-08-02 22:05:09,544 - 日志 - INFO - 当前进程:SpawnPoolWorker-1, 处理数据:5
'''
总结
以上介绍了Python多线程的两种实现方式。由于GIL的存在,多线程无法实现真正的并行执行,限制了其在CPU密集型任务中的性能。为此,Python引入了 multiprocessing多进程模块,通过创建独立的进程,且每个进程有独立的GIL,有效绕过GIL限制,实现真正的并行计算,充分发挥多核CPU的性能优势。
关注公众号:咖啡Beans
在这里,我们专注于软件技术的交流与成长,分享开发心得与笔记,涵盖编程、AI、资讯、面试等多个领域。无论是前沿科技的探索,还是实用技巧的总结,我们都致力于为大家呈现有价值的内容。期待与你共同进步,开启技术之旅。