Python实现多线程的2种方式

53 阅读8分钟

摘要

本文用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 = [1234567891011]
    # 分批将数据放入优先队列中
    for i in range(0len(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 = [1234567891011]
    # 分批将数据放入列表中
    batches = [dataList[i:i+5for i inrange(0len(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、资讯、面试等多个领域。无论是前沿科技的探索,还是实用技巧的总结,我们都致力于为大家呈现有价值的内容。期待与你共同进步,开启技术之旅。