簡體   English   中英

multiprocessing.Pool:使用apply_async的回調選項時調用輔助函數

[英]multiprocessing.Pool: calling helper functions when using apply_async's callback option

apply_async的流程如何在調用iterable(?)函數和回調函數之間起作用?

設置:我正在讀取2000文件目錄中的所有文件的一些行,一些有數百萬行,有些只有少數幾行。 提取一些標題/格式/日期數據以對每個文件進行特征化。 這是在16 CPU機器上完成的,因此對它進行多處理是有意義的。

目前,預期的結果被發送到一個列表( ahlala ),所以我可以打印出來; 之后,這將被寫入* .csv。 這是我的代碼的簡化版本,最初基於這個非常有用的帖子。

import multiprocessing as mp

def dirwalker(directory):
  ahlala = []

  # X() reads files and grabs lines, calls helper function to calculate
  # info, and returns stuff to the callback function
  def X(f): 
    fileinfo = Z(arr_of_lines) 
    return fileinfo 

  # Y() reads other types of files and does the same thing
  def Y(f): 
    fileinfo = Z(arr_of_lines)
    return fileinfo

  # results() is the callback function
  def results(r):
    ahlala.extend(r) # or .append, haven't yet decided

  # helper function
  def Z(arr):
    return fileinfo # to X() or Y()!

  for _,_,files in os.walk(directory):
    pool = mp.Pool(mp.cpu_count()
    for f in files:
      if (filetype(f) == filetypeX): 
        pool.apply_async(X, args=(f,), callback=results)
      elif (filetype(f) == filetypeY): 
        pool.apply_async(Y, args=(f,), callback=results)

  pool.close(); pool.join()
  return ahlala

注意,如果我將所有Z() (輔助函數)放入X()Y()results() ,代碼就可以工作,但這是重復還是可能慢? 我知道每個函數調用都會調用回調函數,但是何時調用回調函數? 是在pool.apply_async() ...完成進程的所有作業? 如果在第一個函數pool.apply_async()的范圍(?)內調用這些輔助函數,那么它應該更快嗎? pool.apply_async()需要(在這種情況下, X() )? 如果沒有,我應該把幫助函數放在results()嗎?

其他相關的想法:守護進程是否為什么沒有出現? 我也很困惑如何排隊,如果這是問題。 這似乎是一個開始學習它的地方 ,但是在使用apply_async ,或者只是在顯着的時間效率低下,可以安全地忽略排隊嗎?

你在這里詢問了很多不同的東西,所以我會盡力覆蓋它:

一旦工作進程返回其結果,您傳遞給callback的函數將在主進程(而不是worker)中執行。 它在Pool對象內部創建的線程中執行。 該線程使用result_queue對象,該對象用於從所有工作進程獲取結果。 線程將結果從隊列中拉出后,它會執行callback 當您的回調正在執行時,不能從隊列中提取其他結果,因此回調快速完成非常重要。 在您的示例中,只要您通過apply_asyncXY進行的apply_async完成,結果將由worker進程放入result_queue ,然后結果處理線程將結果從result_queue ,並且您的callback將被執行。

其次,我懷疑你沒有看到你的示例代碼發生任何事情的原因是因為所有的工作函數調用都失敗了。 如果worker函數失敗,則永遠不會執行callback 除非您嘗試從apply_async調用返回的AsyncResult對象中獲取結果,否則根本不會報告失敗。 但是,由於您沒有保存任何這些對象,因此您永遠不會知道發生的故障。 如果我是你,我會在你測試時嘗試使用pool.apply ,這樣你就會在發生錯誤時立即看到錯誤。

工人可能失敗的原因(至少在你提供的示例代碼中)是因為XY被定義為另一個函數內的函數。 multiprocessing將函數和對象傳遞給工作進程,方法是在主進程中對它們進行pickle,並在工作進程中對它們進行unpickling。 在其他函數內定義的函數不可選,這意味着multiprocessing將無法在工作進程中成功取消它們。 要解決此問題,請在模塊的頂層定義兩個函數,而不是嵌入dirwalker函數。

你肯定應該繼續從XY調用Z ,而不是results 這樣, Z可以在所有工作進程中同時運行,而不必在主進程中一次運行一個調用。 請記住,您的callback函數應該盡可能快,因此您不會保留處理結果。 在那里執行Z會減慢速度。

這里有一些簡單的示例代碼,與您正在執行的操作類似,希望能讓您了解代碼的外觀:

import multiprocessing as mp
import os

# X() reads files and grabs lines, calls helper function to calculate
# info, and returns stuff to the callback function
def X(f): 
    fileinfo = Z(f) 
    return fileinfo 

# Y() reads other types of files and does the same thing
def Y(f): 
    fileinfo = Z(f)
    return fileinfo

# helper function
def Z(arr):
    return arr + "zzz"

def dirwalker(directory):
    ahlala = []

    # results() is the callback function
    def results(r):
        ahlala.append(r) # or .append, haven't yet decided

    for _,_,files in os.walk(directory):
        pool = mp.Pool(mp.cpu_count())
        for f in files:
            if len(f) > 5: # Just an arbitrary thing to split up the list with
                pool.apply_async(X, args=(f,), callback=results)  # ,error_callback=handle_error # In Python 3, there's an error_callback you can use to handle errors. It's not available in Python 2.7 though :(
            else:
                pool.apply_async(Y, args=(f,), callback=results)

    pool.close()
    pool.join()
    return ahlala


if __name__ == "__main__":
    print(dirwalker("/usr/bin"))

輸出:

['ftpzzz', 'findhyphzzz', 'gcc-nm-4.8zzz', 'google-chromezzz' ... # lots more here ]

編輯:

您可以使用multiprocessing.Manager類創建在父進程和子進程之間共享的dict對象:

pool = mp.Pool(mp.cpu_count())
m = multiprocessing.Manager()
helper_dict = m.dict()
for f in files:
    if len(f) > 5:
        pool.apply_async(X, args=(f, helper_dict), callback=results)
    else:
        pool.apply_async(Y, args=(f, helper_dict), callback=results)

然后讓XY接受一個名為helper_dict的第二個參數(或者你想要的任何名字),然后你就完成了。

需要注意的是,這可以通過創建包含普通dict的服務器進程來工作,並且所有其他進程通過Proxy對象與該dict進行通信。 因此,每當您閱讀或寫入字典時,您都在進行IPC。 這使它比真正的字典慢很多。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM