Python中的嵌套并行性

时间:2013-06-11 07:04:03

标签: python python-2.7 parallel-processing

我正在尝试使用Python进行多处理器编程。例如,像Fibonacci这样划分和征服算法。程序流程的执行将像树一样分支并并行执行。换句话说,我们有一个nested parallelism的例子。

从Java开始,我使用线程池模式来管理资源,因为程序可以非常快速地扩展并创建太多短期线程。可以通过ExecutorService实例化单个静态(共享)线程池。

我希望 Pool 也一样,但看起来Pool object is not to be globally shared。例如,使用multiprocessing.Manager.Namespace()共享池会导致错误。

  

池对象不能在进程之间传递或被pickle

我有一个由两部分组成的问题:

  1. 我在这里缺少什么;为什么不应该在进程之间共享池?
  2. 用于在Python中实现嵌套并行性的模式是什么?如果可能,维护递归结构,而不是将其交易以进行迭代。

  3. 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()
    

    爪哇

    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();            
      }    
    
    }
    

    我不确定这里是否重要,但我忽略了“过程”和“线程”之间的区别;对我来说,他们都意味着“虚拟处理器”。我的理解是,池的目的是共享“池”或资源。运行任务可以向池发出请求。当并行任务在其他线程上完成时,可以回收这些线程并将其分配给新任务。禁止共享池是没有意义的,因此每个线程必须实例化自己的新池,因为这似乎会破坏线程池的目的。

2 个答案:

答案 0 :(得分:3)

  

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))

完全不科学但在我的计算机上,上面的代码比其线程等效运行 9x 更快。

我希望这会有所帮助。

答案 1 :(得分:0)

  

1。我在这里想念的是什么;为什么不应该在进程之间共享池?

您通常无法在进程之间共享操作系统线程,无论语言如何。

您可以安排与工作进程共享池管理器的访问权限,但这可能不是解决任何问题的好方法;见下文。

  

2。在Python中实现嵌套并行性的模式是什么?如果可能的话,维护一个递归结构,而不是交换迭代。

这很大程度上取决于您的数据。

在CPython上,一般的答案是使用实现高效并行操作的数据结构。一个很好的例子是NumPy优化的数组类型:here是使用它们在多个处理器核心之间拆分大数组操作的一个例子。

使用阻塞递归实现的Fibonacci函数对于任何基于工作池的方法都是特别适合的,但是:fib(N)将花费大部分时间来绑定N工人除了等待其他工作人员之外什么都不做。还有很多其他方法可以专门处理斐波那契函数(例如,使用CPS来消除阻塞并填充一定数量的工人),但最好根据您将要解决的实际问题来决定策略而不是像这样的例子。