map实现python多线程

目录

Python 多线程、多进程的轻量化实现笔记(翻译 + 应用补充)。

问题:传统生产者/消费者模式代码冗余

import time
import threading
import Queue

class Consumer(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self._queue = queue
    
    def run(self):
        while True:
            msg = self._queue.get()
            if isinstance(msg, str) and msg == 'quit':
                break
            print(“I'm a thread, and I received %s!!” % msg)
        print('Bye byes!')

def Producer():
    queue = Queue.Queue()
    worker = Consumer(queue)
    worker.start()
    start_time = time.time()
    while time.time() - start_time < 5:
        queue.put('something at %s' % time.time())
        time.sleep(1)
    queue.put('quit')
    worker.join()

需要定义类、管理队列、join 操作,代码样板化且易出错。

解决方案:用 map + ThreadPool

核心库:multiprocessing.dummy(线程版)和 multiprocessing(进程版)。

from multiprocessing.dummy import Pool as ThreadPool

# 最简单的用法
pool = ThreadPool(4)  # 4 个线程
results = pool.map(urllib2.urlopen, urls)
pool.close()
pool.join()

等价于传统的 40+ 行代码,核心只有 1 行。

性能对比(6000 张图片生成缩略图)

import os
from multiprocessing import Pool
from PIL import Image

SIZE = (75, 75)
SAVE_DIRECTORY = 'thumbs'

def get_image_paths(folder):
    return (os.path.join(folder, f) for f in os.listdir(folder) if 'jpeg' in f)

def create_thumbnail(filename):
    im = Image.open(filename)
    im.thumbnail(SIZE, Image.ANTIALIAS)
    base, fname = os.path.split(filename)
    save_path = os.path.join(base, SAVE_DIRECTORY, fname)
    im.save(save_path)

if __name__ == '__main__':
    folder = os.path.abspath('11_18_2013_R000_IQM_Big_Sur_Mon__e10d1958e7b766c3e840')
    os.mkdir(os.path.join(folder, SAVE_DIRECTORY))
    images = get_image_paths(folder)
    
    # 单进程:27.9 秒
    # for image in images:
    #     create_thumbnail(image)
    
    # 多进程:5.6 秒
    pool = Pool()
    pool.map(create_thumbnail, images)
    pool.close()
    pool.join()

核心概念

  • multiprocessing.dummy:multiprocessing 的线程版本克隆(IO 密集型任务)
  • multiprocessing.Pool:进程池(CPU 密集型任务)
  • Pool 参数:processes 指定线程/进程数,默认为 CPU 核数

GIL 影响

  • Python 线程受全局解释器锁(GIL)限制,任一时刻仅一线程可用解释器,故为”并发”不”并行”
  • IO 密集型任务用多线程(IO 期间释放解释器,其他线程可执行)
  • CPU 密集型任务用多进程(绕过 GIL)

实际应用数据

单线程: 14.4 秒
4 线程池: 3.1 秒
8 线程池: 1.4 秒
13 线程池: 1.3 秒(线程数 >9 收益有限)

线程数过多会因切换开销反而降速,需通过实验找最优值。

#python