首页 今日头条正文

译者:caspar,译文:

https://segmentfault.com/a/1190000000414339 

原文:https://medium.com/building-things-on-the-internet/40e9b2b36148

Python 在程序并行化方面多少有些身败名裂。放下技能上的问题,例如线程的完结和 GIL,我觉得过错的教育辅导才是首要问题。常见的经典 Python 多线程、多进程教程多显得偏"重"。而且往往不得要领,没有深入探讨日常作业中最有用的内容。

传统的比如

简略查找下"Python 多线程教程",不难发现简直一切的教程都给出触及类和行列的比如

import os 
import PIL

from multiprocessing import Pool
from PIL import Image

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

def get_imag新婚夜婆婆e_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)

   pool = Pool()
   pool.map(creat_thumbnail, images)
   pool.close()
   pool.join()

哈,看起来有些像 Java 不是吗?

我并不是说运用出产者/顾客模型处理多线程/多进程使命是过错的(绿母族事实上,这一模型自有其用武之地)。仅仅,处理日常脚本白龙马儿歌,一行 Python 代码完结并行,骚技能!,克拉玛依使命时咱们能够运用更有功率的模型。

问题在于…

首要,你需求一个样板类; 
其次,你需求一个行列来传递目标; 
而且,你还需求在通道两头都构建相应的办法来帮忙其作业(假如需想要进行双向通信或是保存成果还需求再引进一个行列)。

worker 越多,问题越多

依照这一思路,你现在需求一个 worker 线程的线程池。下面是一篇 IBM 经典教程中的比如——在进行网页检索时经过多线程进行加快。

#Example2.py
'''
A more realistic thread pool example
'''


import time
import threading
import Queue
import urllib2

class Consumer(threading.Thread):
   def __init__(self, queue):
       threading.Thread.__init__(self)
       sel捆绑式f._queue = queue

   def run(self):
       while True:
           content = self._queue.get()
           if isinstance(content, str) and con虞双双tent == 'quit':
        &国产父女nbsp;      break
           response = urllib2.urlopen(content)
       print 'Bye byes!'

def Pr张均若oducer():
   urls = [
       'http://www.python.org', 'http://www.yahoo.com'
       'http://www.scala.org', 'http://www.google.com'
       # etc..
   ]
   queue = Queue.Queue()
   worker_threa白龙马儿歌,一行 Python 代码完结并行,骚技能!,克拉玛依ds = build_worker_pool(queue, 4)
   start_time = time.time()

   # Add the urls to process
   for url in urls:
       queue.put(url)  
   # Ad李久衍d the poison pillv
   for worker in worker_threads:
    &n赤道银行是什么意思bsp;  queue.put('quit')
   for worker in worker_threads:
       worker.join()

   print 'Done! Time taken: {}'.format(time.time() - start_time)

def build_worker_pool(queue, size):
   workers = []
   for _ in range(size):
       worker = Consumer(queue)
       worker.start(美人动态凶恶)
       workers.append(worker)
   return workers

if __name__ == '__main__':
   黛欣燃Producer()

这段代码能正确的运转,但细心看看咱们需求做些什么:结构不同的办法、追寻一系列的线程,还有为了处理恼人的死锁问题,咱们需求进行一系列的 join 操作。这还仅仅开端……

至此咱们回忆了经典的多线程教程,多少有些空泛不是吗?样板化而且易犯错,这样得不偿失的风格显着不那么合适日常运用,好在咱们还有更好的办法。

何不试试 map

map 这一细巧精美的函数是简捷完结 白龙马儿歌,一行 Python 代码完结并行,骚技能!,克拉玛依Python 程序并行化的要害。map 源于 Lisp 这类函数式编程言语。它能够经过一个序列完结两个函数之间的映射。

    urls = ['http://www.yahoo.com', 'http://www.reddit.com']
   results = map(urllib2.urlopen, urls)

上面的这两行代码将 urls 这一序列中的每个元素作为参数传递到 urlopen 办法中,并将一切成果保存到 results 这一列表中。其成果大致相当于:

results = []
for url in urls:
   results.append(urllib2.urlopen(url))

map 函数一手包办了序列操作、参数传递和成果保存等一系列的操作。

为什么这很重要呢?这是因为凭借正确的库,map 能够轻松完结并行化操作。

在 Python 中有个两个库包括了 map 函数: multiprocessing 和它不为人知的子库 multiprocessing.dummy.

这儿多扯两句: multiprocessing.dummy? mltiprocessing 库的线程版克隆?这是虾米?即便在 multiprocessing 库的官方文档里关于这一子库也只要一句相关描绘。而这句描绘译成人话根本便是说:"嘛,有这么个东西,你知道就成."信任我,这个库被严峻轻视了!

dummy 是 multiprocessing 模块的完好克隆,仅有的不同在于 multiprocessing 作用于进程,而 dummy 模块作用于线程(因而也包括了 Python 一切常见的多线程约束)。 
所以替换运用这两个库反常简略。你能够针对 IO 密集型使命和 CPU 密集型使命来挑选不同的库。

着手测验

运用下面的两行代码来引证包括并行化 map 函数的库:

from multiprocessing import Pool
from multiprocessing.dummy import Pool as ThreadPool

实例化 Pool 目标:

pool = ThreadPool()

这条简略的句子替代了 example2.py 中 buildworkerpool 函数 7 行代码的作业。它生成了一系列的 worker 线程并完结初始化作业、将它们储存在变量中以便利拜访。

Pool 目标有一些参数,这儿我所需求重视的只白龙马儿歌,一行 Python 代码完结并行,骚技能!,克拉玛依是它的第一个参数:processes. 这白龙马儿歌,一行 Python 代码完结并行,骚技能!,克拉玛依一参数用于设定线程池中的线程数。其默认值为当时机器 CPU 的核数。

一般来说,履行 CPU 密集型使命时,调用越多的核速度就越快。可是当处理网络密集型使命时,作业有有些难以估计了,经过试验来确认线程池的巨细才是正确的。

pool = ThreadPool(4) # Sets the pool size to 4

线程数过多时,切换线程所耗费的时刻甚至会超越实践作业时刻。关于不同的作业,经过测验来找到线程池巨细的最优值是个不错的主见。

创建好 Pool 目标后,并行化的程序便呼之欲出了。咱们来看看改写后的 example2.py

import urllib2 
from 白龙马儿歌,一行 Python 代码完结并行,骚技能!,克拉玛依multiprocessing.dummy import Pool as ThreadPool

urls = [
   'http://www.python.org',
   'http://www.python.org/about/',
   'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
   'http://www.python.org/doc/',
   'http://www.python.org/download/',
   'http://www.python.org/getit/',
   'http://www.python.org/community/',
   'https://wiki.python.org/moin/',
   'http://planet.p陆鉴成ython.org/',
   'https://wiki.python.org/moin/LocalUserGroups',
   'http://www.python.org/psf/',
   'http://docs.python.org/devguide/',
   'http://www.python.org/community/awards/'
   # etc..
   ]

# Make the Pool of workers
pool = ThreadPool(4)
# Open the urls in their own threads
# and return the results
results = pool.map(urllib2.urlopen, urls)
#close the pool and wait forbondik the work to finish
pool.close()
pool.join()

实践起作用的代码只要 4 行,其间只要一行是要害的。map 函数垂手可得的替代了前文中超越 40 行的比如。为了更风趣一些,我计算了不同办法、不同线程池巨细的耗时状况。

# results = [] 
# for url in urls:
#   result = urllib2.urlopen(url)
#   results.append(result)

# # ------- VERSUS ------- #

# # --刘之冰前妻冯丽萍----- 4 Pool ------- #
# pool = ThreadPool(4)
# results = pool.map(url白龙马儿歌,一行 Python 代码完结并行,骚技能!,克拉玛依lib2.urlopen, urls)

# # ------- 8 Pool ------- #

# pool = ThreadPool(8)
# results = pool.map(urllib2.urlopen, urls)

# # ------- 13 Pool ------- #

# pool = ThreadPool(13)
# results = pool.map(urllib2.urlopen, urls)

成果:

#        Single thread:  14.4 Seconds 
#               4 Pool:   3.1 Seconds
#               8 Pool:   1.4 Seconds
#              13 Pool:   1.3 Seconds

很棒的成果不是吗?这一成果也说明晰为什么要经过试验来确认线程池的巨细。在我的机器受骗线程池巨细大于 9 带来的收益就非常有限了。

另一个实在的比如

生成上千张图片的缩略图 
这是一个 CPU 密集型的使命,而且非常合适进行并行化。

根底单进程版别

import os 
import PIL

fr最原始的愿望txtom 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(fold梁亮亮和谢细姨的简略故事er)
           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)

   for image in images:
       create_thumbnail(Image)

上边这段代码的首要作业便是将遍历传入的文件夹中的图片文件,逐个生成缩略图,并将这些缩略图保存到特定文件夹中。

这我的机器上,用这一程序处理 6000 张图片需求花费 27.9 秒。

假如咱们运用 map 函数来替代 for 循环:

import os 
import PIL

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(f满胜男older, SAVE_DIRECTORY))

   images = get_image_paths(folder)

   pool = Pool()
   pool.map(creat_thumbnail, images)
   pool.close()
   pool.join()

5.6 秒!

尽管只改动了几行代码,咱们却显着进步了程序的履行速度。在出产环境中,咱们能够为 CPU 密集型使命和 IO 密集型使命别离挑选多进程和多线程库来进九尾忆情一步进步履行速度——这也是处理死锁问题的良方。此外,因为 map 函数并不支撑手动线程办理,反而使得相关的 debug 作业也变得反常简略。

到这儿,咱们就完结了(根本)经过一行 Pyt张雄伟赵竑hon 完结并行化。

◆ ◆ ◆  ◆ 


●编号625,输入编号直达本文

●输入m获取文章目录

引荐↓↓↓

人工智能与大数据技能

更多引荐俏厨娘不嫁闷将军25个技能类大众微信

包括:程序人生、算法与数据结构、黑客技能与网络安全、大数据技能、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等。

版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。