Многопоточная генерация миниатюр в Python - PullRequest
2 голосов
/ 28 апреля 2011

Я хотел бы создать каталог изображений и создать миниатюры для каждого изображения. У меня на машине 12 используемых ядер. Какой хороший способ использовать их? У меня нет большого опыта написания многопоточных приложений, поэтому любой простой пример кода приветствуется. Заранее спасибо.

Ответы [ 3 ]

7 голосов
/ 28 апреля 2011

Аннотация

Используйте процессы, а не потоки, потому что 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()

Следуя комментарию соулмэна, вы можете воспользоваться предоставленным процессом pull.

Я адаптировал некоторый код из multiprocessing manual. Обратите внимание, что вам, вероятно, следует использовать multiprocessing.cpu_count() вместо 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 полезен для запуска внешних процессов и поэтому предпочтителен, если вы планируете использовать внешний инструмент для создания миниатюр, такой как imagemagick '* convert. Пример кода:

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()), удалите его и добавьте новый процесс в свой список.

2 голосов
/ 28 апреля 2011

Как и другие ответили, подпроцессы обычно предпочтительнее потоков. 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 имеет смысл, если каждая файловая операция является относительно медленнойпо сравнению с общением с каждым подпроцессом.

1 голос
/ 28 апреля 2011

Не ходите с нитями, они слишком сложны для того, что вы хотите.Вместо этого используйте библиотеку подпроцессов, чтобы порождать отдельные процессы, работающие в каждом каталоге.

Таким образом, у вас будет основная программа, которая генерирует список файлов, затем начинает выталкивать каждый файл из списка и подавать его в подпроцесс.Подпроцесс был бы простой программой на Python для генерации миниатюры из входного изображения.Некоторая простая логика, чтобы держать ваши порожденные процессы в ограниченном наборе, скажем, 11, не позволила бы вам бомбить вашу машину.

Это позволяет операционной системе обрабатывать все эти мелкие детали того, кто где работает и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...