Python中的多线程缩略图生成

4 投票
3 回答
1421 浏览
提问于 2025-04-16 16:35

我想要遍历一个包含图片的文件夹,并为每张图片生成缩略图。我的电脑有12个可以用的核心。有没有什么好的方法来利用这些核心呢?我对写多线程程序不太有经验,所以如果能提供一些简单的示例代码就太好了。提前谢谢你们!

3 个回答

1

别用线程,线程太复杂了,不适合你想做的事情。相反,使用子进程库来启动独立的进程,逐个处理每个目录。

你可以先写一个主程序,生成一个文件列表,然后一个一个地取出文件,把它们传给子进程。这个子进程可以是一个简单的Python程序,用来从输入的图片生成缩略图。你可以加一些简单的逻辑,限制同时运行的进程数量,比如最多11个,这样就不会让你的电脑崩溃。

这样做可以让操作系统处理那些繁琐的细节,比如哪个进程在哪里运行等等。

3

就像其他人回答的那样,使用子进程通常比使用线程更好。multiprocessing.Pool 让你可以轻松地使用你想要的子进程数量,比如这样:

import os
from multiprocessing import Pool

def process_file(filepath):
    [if filepath is an image file, resize it]

def enumerate_files(folder):
    for dirpath, dirnames, filenames in os.walk(folder):
       for fname in filenames:
           yield os.path.join(dirpath, fname)

if __name__ == '__main__':
    pool = Pool(12) # or omit the parameter to use CPU count
    # use pool.map() only for the side effects, ignore the return value
    pool.map(process_file, enumerate_files('.'), chunksize=1)

这里的chunksize=1参数是有道理的,特别是当每个文件操作相对比较慢,而与每个子进程的通信比较快的时候。

8

摘要

使用进程,而不是线程,因为在处理需要大量计算的任务时,Python的线程效率不高,这主要是因为有个叫做GIL的东西。

对于多进程,有两个可能的解决方案:

multiprocessing模块

如果你在使用内部的缩略图生成器(比如PIL),那么这个模块是比较推荐的。你只需要写一个生成缩略图的函数,然后同时启动12个进程。当其中一个进程完成后,就可以在它的位置再启动一个新的进程。

根据Python文档,这里有个脚本可以利用12个核心:

from multiprocessing import Process
import os

def info(title):  # For learning purpose, remove when you got the PID\PPID idea
    print title
    print 'module:', __name__
    print 'parent process:', os.getppid(), 
    print 'process id:', os.getpid()
 
def f(name):      # Working function
    info('function f')
    print 'hello', name

if __name__ == '__main__':
    info('main line')
    processes=[Process(target=f, args=('bob-%d' % i,)) for i  in range(12)]
    [p.start() for p in processes]
    [p.join()  for p in processes]

附录:使用multiprocess.pool()

根据soulman的评论,你可以使用提供的进程池。

我从multiprocessing手册中调整了一些代码。注意,你应该使用multiprocessing.cpu_count()来自动确定CPU的数量,而不是写死数字4

from multiprocessing import Pool
import datetime

def f(x):  # You thumbnail maker function, probably using some module like PIL
    print '%-4d: Started at %s' % (x, datetime.datetime.now())
    return x*x

if __name__ == '__main__':
    pool = Pool(processes=4)              # start 4 worker processes
    print pool.map(f, range(25))          # prints "[0, 1, 4,..., 81]"

这样会得到(注意输出的顺序可能不是严格的!):

0   : Started at 2011-04-28 17:25:58.992560
1   : Started at 2011-04-28 17:25:58.992749
4   : Started at 2011-04-28 17:25:58.992829
5   : Started at 2011-04-28 17:25:58.992848
2   : Started at 2011-04-28 17:25:58.992741
3   : Started at 2011-04-28 17:25:58.992877
6   : Started at 2011-04-28 17:25:58.992884
7   : Started at 2011-04-28 17:25:58.992902
10  : Started at 2011-04-28 17:25:58.992998
11  : Started at 2011-04-28 17:25:58.993019
12  : Started at 2011-04-28 17:25:58.993056
13  : Started at 2011-04-28 17:25:58.993074
14  : Started at 2011-04-28 17:25:58.993109
15  : Started at 2011-04-28 17:25:58.993127
8   : Started at 2011-04-28 17:25:58.993025
9   : Started at 2011-04-28 17:25:58.993158
16  : Started at 2011-04-28 17:25:58.993161
17  : Started at 2011-04-28 17:25:58.993179
18  : Started at 2011-04-28 17:25:58.993230
20  : Started at 2011-04-28 17:25:58.993233
19  : Started at 2011-04-28 17:25:58.993249
21  : Started at 2011-04-28 17:25:58.993252
22  : Started at 2011-04-28 17:25:58.993288
24  : Started at 2011-04-28 17:25:58.993297
23  : Started at 2011-04-28 17:25:58.993307
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 
 289, 324, 361, 400, 441, 484, 529, 576]

subprocess模块

subprocess模块非常适合运行外部进程,因此如果你打算使用像imagemagickconvert这样的外部缩略图生成器,这个模块是比较合适的。代码示例:

import subprocess as sp

processes=[sp.Popen('your-command-here', shell=True, 
                    stdout=sp.PIPE, stderr=sp.PIPE) for i in range(12)]

现在,遍历这些进程。如果有任何一个进程完成了(可以使用subprocess.poll()),就把它移除,并在你的列表中添加一个新的进程。

撰写回答