簡體   English   中英

Python中的嵌套並行性

[英]Nested parallelism in Python

我正在嘗試使用Python進行多處理器編程。 例如,像Fibonacci那樣采用分治算法。 程序流程的執行將像樹一樣分支並並行執行。 換句話說,我們有一個嵌套並行性的例子。

從Java開始,我使用線程池模式來管理資源,因為程序可以非常快速地擴展並創建太多短期線程。 可以通過ExecutorService實例化單個靜態(共享)線程ExecutorService

我希望Pool也一樣 ,但看起來Pool對象不是全局共享的 例如,使用multiprocessing.Manager.Namespace()共享池將導致錯誤。

池對象不能在進程之間傳遞或被pickle

我有一個由兩部分組成的問題:

  1. 我在這里想念的是什么; 為什么不應該在進程之間共享池?
  2. 在Python中實現嵌套並行性模式是什么 如果可能的話,維護一個遞歸結構,而不是交換迭代。

from concurrent.futures import ThreadPoolExecutor

def fibonacci(n):
    if n < 2:
        return n
    a = pool.submit(fibonacci, n - 1)
    b = pool.submit(fibonacci, n - 2)
    return a.result() + b.result()

def main():
    global pool

    N = int(10)
    with ThreadPoolExecutor(2**N) as pool:
        print(fibonacci(N))

main()

Java的

public class FibTask implements Callable<Integer> {

    public static ExecutorService pool = Executors.newCachedThreadPool();
    int arg;

    public FibTask(int n) {
        this.arg= n;
    }

    @Override
    public Integer call() throws Exception {
        if (this.arg > 2) { 
            Future<Integer> left = pool.submit(new FibTask(arg - 1));
            Future<Integer> right = pool.submit(new FibTask(arg - 2));
            return left.get() + right.get();
        } else {
            return 1;
        }

    } 

  public static void main(String[] args) throws Exception {
      Integer n = 14;
      Callable<Integer> task = new FibTask(n);
      Future<Integer> result =FibTask.pool.submit(task); 
      System.out.println(Integer.toString(result.get()));
      FibTask.pool.shutdown();            
  }    

}

我不確定這里是否重要,但我忽略了“過程”和“線程”之間的區別; 對我來說,他們都意味着“虛擬處理器”。 我的理解是,池的目的是共享“池”或資源。 運行任務可以向池發出請求。 當並行任務在其他線程上完成時,可以回收這些線程並將其分配給新任務。 禁止共享池是沒有意義的,因此每個線程必須實例化自己的新池,因為這似乎會破壞線程池的目的。

1)我在這里缺少什么; 為什么不應該在進程之間共享池?

並非所有對象/實例都是可選擇/可序列化的,在這種情況下,池使用不可選擇的threading.lock:

>>> import threading, pickle
>>> pickle.dumps(threading.Lock())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
[...]
  File "/Users/rafael/dev/venvs/general/bin/../lib/python2.7/copy_reg.py", line 70, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle lock objects

或更好:

>>> import threading, pickle
>>> from concurrent.futures import ThreadPoolExecutor
>>> pickle.dumps(ThreadPoolExecutor(1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1374, in dumps
    Pickler(file, protocol).dump(obj)
  File 
[...]
"/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 306, in save
        rv = reduce(self.proto)
      File "/Users/rafael/dev/venvs/general/bin/../lib/python2.7/copy_reg.py", line 70, in _reduce_ex
        raise TypeError, "can't pickle %s objects" % base.__name__
    TypeError: can't pickle lock objects

如果你考慮它,它是有道理的,鎖是由操作系統管理的信號量原語(因為python使用本機線程)。 能夠在python運行時內腌制並保存該對象狀態實際上無法實現任何有意義的事情,因為它的真實狀態由操作系統保存。

2)在Python中實現嵌套並行性的模式是什么? 如果可能的話,維護一個遞歸結構,而不是交換迭代

現在,為了聲望,我上面提到的所有內容並不真正適用於您的示例,因為您使用的是線程(ThreadPoolExecutor)而不是進程(ProcessPoolExecutor),因此不必跨進程進行數據共享。

您的java示例似乎更有效,因為您正在使用的線程池(CachedThreadPool)正在根據需要創建新線程,而python執行器實現是有界的並且需要顯式的最大線程數(max_workers)。 這些語言之間存在一些語法差異似乎也讓你失望(python中的靜態實例本質上是任何未明確限定的)但實質上這兩個示例都會創建完全相同數量的線程以便執行。 例如,這是一個在python中使用相當天真的CachedThreadPoolExecutor實現的示例:

from concurrent.futures import ThreadPoolExecutor

class CachedThreadPoolExecutor(ThreadPoolExecutor):
    def __init__(self):
        super(CachedThreadPoolExecutor, self).__init__(max_workers=1)

    def submit(self, fn, *args, **extra):
        if self._work_queue.qsize() > 0:
            print('increasing pool size from %d to %d' % (self._max_workers, self._max_workers+1))
            self._max_workers +=1

        return super(CachedThreadPoolExecutor, self).submit(fn, *args, **extra)

pool = CachedThreadPoolExecutor()

def fibonacci(n):
    print n
    if n < 2:
        return n
    a = pool.submit(fibonacci, n - 1)
    b = pool.submit(fibonacci, n - 2)
    return a.result() + b.result()

print(fibonacci(10))

性能調整:

我強烈建議調查gevent,因為它會在沒有線程開銷的情況下為您提供高並發性。 情況並非總是如此,但您的代碼實際上是gevent使用的典型代表。 這是一個例子:

import gevent

def fibonacci(n):
    print n
    if n < 2:
        return n
    a = gevent.spawn(fibonacci, n - 1)
    b = gevent.spawn(fibonacci, n - 2)
    return a.get()  + b.get()

print(fibonacci(10))

完全不科學,但在我的計算機上,上面的代碼運行速度比其螺紋等效快9倍。

我希望這有幫助。

我在這里錯過了什么; 為什么不應該在進程之間共享池?

無論語言如何,您通常都無法在進程之間共享OS線程。

您可以安排與工作進程共享池管理器的訪問權限,但這可能不是解決任何問題的好方法; 見下文。

2.在Python中實現嵌套並行性的模式是什么? 如果可能的話,維護一個遞歸結構,而不是交換迭代。

這在很大程度上取決於您的數據。

在CPython上,一般的答案是使用實現高效並行操作的數據結構。 NumPy優化的陣列類型就是一個很好的例子: 這里有一個使用它們在多個處理器核心之間拆分大陣列操作的例子。

使用阻塞遞歸實現的Fibonacci函數對於任何基於工作池的方法都是特別適合的,但是:fib(N)將花費大部分時間來綁定N個工作人員,除了等待其他工作者之外什么都不做。 還有許多其他方法可以專門處理斐波那契函數(例如,使用CPS來消除阻塞並填充恆定數量的工人),但最好根據您將要解決的實際問題來決定您的策略,而不是示例像這樣。

暫無
暫無

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

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