一行代碼實現Python並行處理

2020-09-10 程式設計師面試吧

一行代碼實現Python並行處理

Python 在程序並行化方面多少有些聲名狼藉。撇開技術上的問題,例如線程的實現和 GIL,我覺得錯誤的教學指導才是主要問題。常見的經典 Python 多線程、多進程教程多顯得偏&34;。而且往往隔靴搔癢,沒有深入探討日常工作中最有用的內容。

傳統的例子

簡單搜索下&34;,不難發現幾乎所有的教程都給出涉及類和隊列的例子:

import osimport PILfrom multiprocessing import Poolfrom PIL import ImageSIZE = (75,75)SAVE_DIRECTORY = &39;def get_image_paths(folder):    return (os.path.join(folder, f)            for f in os.listdir(folder)            if &39; 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__ == &39;:    folder = os.path.abspath(        &39;)    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 不是嗎?

我並不是說使用生產者/消費者模型處理多線程/多進程任務是錯誤的(事實上,這一模型自有其用武之地)。只是,處理日常腳本任務時我們可以使用更有效率的模型。

問題在於…

  • 首先,你需要一個樣板類;
  • 其次,你需要一個隊列來傳遞對象;
  • 而且,你還需要在通道兩端都構建相應的方法來協助其工作(如果需想要進行雙向通信或是保存結果還需要再引入一個隊列)。

worker 越多,問題越多

按照這一思路,你現在需要一個 worker 線程的線程池。下面是一篇 IBM 經典教程中的例子——在進行網頁檢索時通過多線程進行加速。

39;&39;A more realistic thread pool example&39;&39;quit&39;Bye byes!&39;http://www.python.org&39;http://www.yahoo.com&39;http://www.scala.org&39;http://www.google.com& etc..    ]    queue = Queue.Queue()    worker_threads = build_worker_pool(queue, 4)    start_time = time.time()     Add the poison pillv    for worker in worker_threads:        queue.put(&39;)    for worker in worker_threads:        worker.join()    print &39;.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 workersif __name__ == &39;:    Producer()

這段代碼能正確的運行,但仔細看看我們需要做些什麼:構造不同的方法、追蹤一系列的線程,還有為了解決惱人的死鎖問題,我們需要進行一系列的 join 操作。這還只是開始……

至此我們回顧了經典的多線程教程,多少有些空洞不是嗎?樣板化而且易出錯,這樣事倍功半的風格顯然不那麼適合日常使用,好在我們還有更好的方法。

何不試試 map

map 這一小巧精緻的函數是簡捷實現 Python 程序並行化的關鍵。map 源於 Lisp 這類函數式程式語言。它可以通過一個序列實現兩個函數之間的映射。

    urls = [&39;, &39;]    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 庫的官方文檔裡關於這一子庫也只有一句相關描述。而這句描述譯成人話基本就是說:&34;相信我,這個庫被嚴重低估了!

dummy 是 multiprocessing 模塊的完整克隆,唯一的不同在於 multiprocessing 作用於進程,而 dummy 模塊作用於線程(因此也包括了 Python 所有常見的多線程限制)。

所以替換使用這兩個庫異常容易。你可以針對 IO 密集型任務和 CPU 密集型任務來選擇不同的庫。

動手嘗試

使用下面的兩行代碼來引用包含並行化 map 函數的庫:

from multiprocessing import Poolfrom multiprocessing.dummy import Pool as ThreadPool

實例化 Pool 對象:

pool = ThreadPool()

這條簡單的語句替代了 example2.py 中 buildworkerpool 函數 7 行代碼的工作。它生成了一系列的 worker 線程並完成初始化工作、將它們儲存在變量中以方便訪問。

Pool 對象有一些參數,這裡我所需要關注的只是它的第一個參數:processes. 這一參數用於設定線程池中的線程數。其默認值為當前機器 CPU 的核數。

一般來說,執行 CPU 密集型任務時,調用越多的核速度就越快。但是當處理網絡密集型任務時,事情有些難以預計了,通過實驗來確定線程池的大小才是明智的。

pool = ThreadPool(4) 39;http://www.python.org&39;http://www.python.org/about/&39;http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html&39;http://www.python.org/doc/&39;http://www.python.org/download/&39;http://www.python.org/getit/&39;http://www.python.org/community/&39;https://wiki.python.org/moin/&39;http://planet.python.org/&39;https://wiki.python.org/moin/LocalUserGroups&39;http://www.python.org/psf/&39;http://docs.python.org/devguide/&39;http://www.python.org/community/awards/& etc..    ] Open the urls in their own threadsclose the pool and wait for the work to finishpool.close()pool.join()

實際起作用的代碼只有 4 行,其中只有一行是關鍵的。map 函數輕而易舉的取代了前文中超過 40 行的例子。為了更有趣一些,我統計了不同方法、不同線程池大小的耗時情況。

 for url in urls:   results.append(result) ------- VERSUS -------   results = pool.map(urllib2.urlopen, urls) ------- 8 Pool -------  pool = ThreadPool(8)  results = pool.map(urllib2.urlopen, urls)

結果:

               4 Pool:   3.1 Seconds              13 Pool:   1.3 Seconds

很棒的結果不是嗎?這一結果也說明了為什麼要通過實驗來確定線程池的大小。在我的機器上當線程池大小大於 9 帶來的收益就十分有限了。

另一個真實的例子

生成上千張圖片的縮略圖

這是一個 CPU 密集型的任務,並且十分適合進行並行化。

基礎單進程版本

import osimport PILfrom multiprocessing import Poolfrom PIL import ImageSIZE = (75,75)SAVE_DIRECTORY = &39;def get_image_paths(folder):    return (os.path.join(folder, f)            for f in os.listdir(folder)            if &39; 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__ == &39;:    folder = os.path.abspath(        &39;)    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 osimport PILfrom multiprocessing import Poolfrom PIL import ImageSIZE = (75,75)SAVE_DIRECTORY = &39;def get_image_paths(folder):    return (os.path.join(folder, f)            for f in os.listdir(folder)            if &39; 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__ == &39;:    folder = os.path.abspath(        &39;)    os.mkdir(os.path.join(folder, SAVE_DIRECTORY))    images = get_image_paths(folder)    pool = Pool()    pool.map(creat_thumbnail, images)    pool.close()    pool.join()

5.6 秒!

雖然只改動了幾行代碼,我們卻明顯提高了程序的執行速度。在生產環境中,我們可以為 CPU 密集型任務和 IO 密集型任務分別選擇多進程和多線程庫來進一步提高執行速度——這也是解決死鎖問題的良方。此外,由於 map 函數並不支持手動線程管理,反而使得相關的 debug 工作也變得異常簡單。

到這裡,我們就實現了(基本)通過一行 Python 實現並行化。

原文轉自:http://dwz.date/bZGa

相關焦點

  • 今天帶大家看看Python一行代碼可以實現哪些事情
    今天小編就帶大家玩玩只用一行python代碼或者命令,看下可以玩一些什麼HTTP 伺服器一行代碼實現函數使用lambda可以用一行代碼實現一個匿名函數,比如想要將列表中的元素進行計算操作,可以直接這樣:
  • Python 只用一行代碼,可以實現哪些事兒?
    今天我們來玩玩,只用一行 Python 代碼或命令,看下可以玩些什麼。可以使用 json.tool 來格式化 Json:python -c使用這個 -c 參數可以直接在終端中使用 Python 簡單的代碼:
  • 如何理解python一行代碼實現一個愛心字符畫?
    前言python中有個很酷的效果,一行代碼實現一個愛心字符,雖說是一行代碼,但是理解起來還是比較難的,括號太多,並且使用了python的一些快捷小技巧。下面通過分解來理解這行代碼,這裡主要理解三元表達式,列表生成式,還有就是循環中的數字為什麼是-30,30和30,-30。三元表達式理解三元表達式其實就是將if else語句一行書寫,格式為:result = 為真實的結果 if 判斷條件 else 為假時的結果。
  • 童年的遊戲,Python一行代碼就能玩
    ,一行代碼就能進入使用Python開發的小遊戲快樂玩耍!安裝與使用安裝當然也很簡單一行代碼就可以pip install freegames由於該項目中的所有遊戲均是基於Python內置模塊Turtle貪吃蛇現在我們可以使用一行代碼啟動相關遊戲,比如貪吃蛇snakepython -m freegames.snake
  • 使用Python實現多線程和多處理方法
    這些方法指導作業系統優化使用系統硬體,從而提高代碼執行效率。 多線程 引用Wiki的解釋—在計算機體系結構中,多線程是指從軟體或者硬體上實現多個線程並發執行的技術。具有多線程能力的計算機因有硬體支持而能夠在同一時間執行多個線程,進而提升整體處理性能。
  • 編程界大佬教你:一行Python代碼能做出哪些神奇的事情?
    import requests(1)一行代碼啟動一個Web服務python -m SimpleHTTPServer(2)一行代碼實現變量值互換a, b = 1, 2; a, b = b, a(9)一行代碼實現快排算法
  • Python 一行代碼,居然能幹這麼多事情
    important}今天我們來玩玩,只用一行 Python 代碼或命令,看下可以玩些什麼。比如我們之前就玩過一行 Python 命令實現 http 服務:>一行代碼實現函數使用 lambda 可以用一行代碼實現一個匿名函數,比如想要將列表中的元素進行計算操作,
  • Python一行代碼,能玩這麼多童年的遊戲?
    >,一行代碼就能進入使用Python開發的小遊戲快樂玩耍!安裝與使用安裝當然也很簡單一行代碼就可以pip install freegames由於該項目中的所有遊戲均是基於Python內置模塊Turtle製作,所以沒有太多依賴,安裝不會有困難。
  • 用一行python代碼輕鬆解決,沒想到它這麼強
    一行python代碼一行python代碼對於學編程的程式設計師來說,接觸到的第一行代碼都是一樣的,就像python程式設計師接觸到的第一行python代碼print("hello world!")而一行python代碼可以做到哪些事情呢?下面羽憶教程為你展示。用一行python代碼列印九九乘法表當你身邊沒有計算器,又需要九九乘法表時,可以通過以下一行簡單的python代碼獲得九九乘法表。
  • 使用python實現九九乘法口訣表,使用這個語法,只需一行代碼
    有一次去面試,被問到了九九乘法口訣用代碼如何實現 ?雖然當時已經做了出來,但是後來才知道,其實有更簡單的方法,只需一行代碼就可以實現 。那麼我們先來看下普通的實現方式,就是做個嵌套循環,然後列印兩個數的乘積信息 。
  • 一行代碼,用Python能做出什麼有趣的功能?
    python有很多優雅有趣的代碼寫法,同時還很簡短,以至於當我剛開始接觸這個程式語言的時候,就愛不釋手。對編程感興趣,對Python感興趣,可以關注我噢·~私信發送「獲取」,更可以免費獲得Python學習資料~python到底有多有趣呢?一行代碼告訴你!
  • 處理表格時的:一行拆多行,多行並一行,Python輕鬆搞定
    前兩天有人提了下面這樣一個問題,其中一個是「一行拆多行」(將單行一列中的多個值分成多行單值),另外一個是「多行並一行」(將多行單值合併為單行一列中的多個值)。這是在對Excel數據表格進行數據處理時經常可能遇到的一個場景,如果是用 Python 做數據處理應該怎樣解決呢?今天我們就來演示一下。
  • 只要一行Python代碼,就能搭建一個共享文件區域網伺服器
    Python命令 一行Python命令如何搭建區域網,其實很簡單,我們用了Python裡面的一條命令就可以搞定!它會創建和偵聽 HTTP 套接字,並將請求調度給處理程序。學習資源分享:11564658132.如何操作搭建本地伺服器的代碼十分簡單,只需要在命令行運行以下代碼即可。
  • 資源| OpenAI開源機器人模擬Python庫mujoco-py:可高效處理並行模擬
    代碼:https://github.com/openai/mujoco-py文檔:https://openai.github.io/mujoco-py/build/html/index.html該庫是 OpenAI 用於深度學習機器人研究的核心工具之一,現在將其作為 mujoco-py(Python 3 的 MuJoCo 綁定)的主要版本發布。
  • 10條很棒的Python一行代碼
    前言自從我用Python編寫第一行代碼以來,我就被它的簡單性、出色的可讀性和特別流行的一行代碼所吸引。在下面,我想介紹並解釋其中一些一行程序—可能有一些您還不知道,但對您的下一個Python項目很有用。
  • 這一行代碼,能讓你的 Python運行速度提高100倍
    一直被病垢運行速度太慢,但是實際上python的執行效率並不慢,慢的是python用的解釋器Cpython運行效率太差。 「一行代碼讓python的運行速度提高100倍」這絕不是譁眾取寵的論調。 我們來看一下這個最簡單的例子,從1一直累加到1億。
  • Python那些事——60行Python代碼,實現多線程PDF轉Word
    今天教大家用60行代碼實現,多線程批量PDF轉Word。沒興趣看具體過程可以直接拉到最後,有代碼。分解任務把PDF轉為Word,分幾步?兩步,第一步讀取PDF文件,第二步寫入Word文件。是的,就是這麼簡單,藉助Python第三方包,可以輕鬆實現上面兩個過程,我們要用到pdfminer3k和 python-docx這兩個包。
  • 2020,可供生產使用的Python 6大並行處理庫
    6.Ipyparallel Ipyparallel是另一個專注於多核處理和任務分發的系統,專門用於跨集群並行地執行Jupyter notebook代碼。已經在Jupyter工作的項目和團隊可以立即開始使用Ipyparallel。
  • 代碼詳解:如何用Python運行高性能的數學範式?
    首先需要明確的是:編寫python代碼和編寫pythonic代碼之間存在很大差異。這篇文章圍繞一些最常用的數據科學操作編寫了最佳(也存在爭議的)實踐。例如通過使用numpy矢量化、並行計算和多線程來評估複雜的數學表達式,對ndarrays進行初始化。
  • Python的一行代碼有何魅力?lambda匿名函數都自嘆不如
    Python學起,接觸這個語言的人對Python這種簡潔的代碼編寫方法都愛不釋手。其中,一行代碼編寫與lambda表達式是最具代表性的方法,下面分別來介紹一行代碼編寫和lambda表達式的代碼之美。一行代碼我們都知道,創建列表的方式有多種,比如直接定義、使用list()函數轉換、使用for循環迭代輸出等等。