2013-06-11 44 views
14

Tôi đang thử lập trình đa xử lý với Python. Lấy một thuật toán phân chia và chinh phục như Fibonacci chẳng hạn. Luồng thực thi chương trình sẽ phân nhánh như một cây và thực hiện song song. Nói cách khác, chúng tôi có một ví dụ về nested parallelism.Song song lồng nhau trong Python

Từ Java, tôi đã sử dụng mẫu mô hình phân luồng để quản lý tài nguyên, vì chương trình có thể phân nhánh rất nhanh và tạo quá nhiều chuỗi ngắn. Một luồng đơn tĩnh (chia sẻ) có thể được khởi tạo qua ExecutorService.

Tôi mong đợi tương tự cho Pool, nhưng có vẻ như Pool object is not to be globally shared. Ví dụ: việc chia sẻ Hồ bơi bằng cách sử dụng multiprocessing.Manager.Namespace() sẽ dẫn đến lỗi.

vật hồ bơi không có thể được thông qua giữa các quá trình hoặc ngâm

Tôi có một câu hỏi 2 phần:

  1. tôi đang thiếu gì ở đây; tại sao một Pool không được chia sẻ giữa các quá trình?
  2. Mẫu để triển khai thực hiện song song lồng nhau trong Python là gì? Nếu có thể, hãy duy trì cấu trúc đệ quy và không giao dịch để lặp lại.

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

} 

Tôi không chắc chắn nếu vấn đề ở đây, nhưng tôi bỏ qua sự khác biệt giữa "quy trình" và "chủ đề"; với tôi cả hai đều có nghĩa là "bộ xử lý ảo". Sự hiểu biết của tôi là, mục đích của một hồ bơi là để chia sẻ một "hồ bơi" hoặc tài nguyên. Các tác vụ đang chạy có thể đưa ra yêu cầu đối với Hồ bơi. Khi các tác vụ song song hoàn thành trên các chủ đề khác, các luồng đó có thể được khai hoang và gán cho các nhiệm vụ mới. Nó không có ý nghĩa với tôi để không cho phép chia sẻ của hồ bơi, để mỗi thread phải nhanh chóng hồ bơi mới của riêng mình, vì điều đó dường như sẽ đánh bại mục đích của một hồ bơi thread.

+0

Tại sao bạn cần nó được chia sẻ trên toàn cầu?Bạn không thể chứa tất cả bên trong một không gian tên/lớp? –

+2

@InbarRose Vấn đề là trong một hàm đệ quy thực thi cuộc gọi đệ quy bên trong một tiến trình khác, nhóm được chia nhỏ và cũng được gọi bởi tiến trình con. Điều này gây ra vấn đề với hàng đợi do đó nó không hoạt động. Dù sao tôi muốn nhấn mạnh rằng trong Java bạn đang sử dụng * chủ đề *. Với chủ đề không có bất kỳ vấn đề vì không có forking của đối tượng hồ bơi. Tôi tin rằng việc sử dụng một hồ bơi quá trình trong Java sẽ dẫn đến, nhiều hơn hoặc ít hơn, cùng một hành vi. – Bakuriu

+0

@InbarRose Tôi cũng đã thử chứa 'Pool' như một thể hiện lớp và biến tĩnh, nhưng vẫn đạt được cùng một vấn đề. Ví dụ, với 'Pool' và các cuộc gọi đệ quy có trong một lớp đơn, nhưng làm như vậy vẫn dẫn đến cùng một vấn đề:> các đối tượng pool không thể được truyền giữa các tiến trình ... –

Trả lời

3

1) Tôi thiếu gì ở đây; tại sao một Pool không được chia sẻ giữa các quá trình?

Không phải tất cả đối tượng/trường hợp được pickable/serializable, trong trường hợp này, hồ bơi sử dụng threading.lock mà không phải là pickable:

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

hoặc tốt hơn:

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

Nếu bạn nghĩ về nó, nó có ý nghĩa, một khóa là một semaphore nguyên thủy được quản lý bởi hệ điều hành (kể từ python sử dụng chủ đề bản địa). Có thể pickle và lưu trạng thái đối tượng bên trong thời gian chạy python sẽ thực sự không thực hiện bất cứ điều gì có ý nghĩa vì trạng thái thực của nó đang được giữ bởi hệ điều hành.

2) Mô hình thực hiện song song lồng nhau trong Python là gì?Nếu có thể, duy trì một cấu trúc đệ quy, và không giao dịch nó cho lặp

Bây giờ, đối với uy tín, tất cả mọi thứ tôi đã đề cập ở trên không thực sự áp dụng đối với ví dụ của bạn kể từ khi bạn đang sử dụng chủ đề (ThreadPoolExecutor) chứ không phải quá trình (ProcessPoolExecutor) vì vậy không có dữ liệu chia sẻ qua quá trình phải xảy ra.

Ví dụ java của bạn dường như hiệu quả hơn vì nhóm chủ đề bạn đang sử dụng (CachedThreadPool) đang tạo chủ đề mới khi cần thiết trong khi triển khai thực thi python bị ràng buộc và yêu cầu số chuỗi tối đa rõ ràng (max_workers). Có một chút khác biệt về cú pháp giữa các ngôn ngữ mà dường như ném bạn đi (các cá thể tĩnh trong python về cơ bản là không có gì rõ ràng) nhưng về cơ bản cả hai ví dụ sẽ tạo chính xác cùng một số luồng để thực thi. Ví dụ, sau đây là một ví dụ sử dụng một thực hiện CachedThreadPoolExecutor khá ngây thơ trong python:

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

Hiệu suất điều chỉnh:

tôi mạnh mẽ đề nghị xem xét gevent vì nó sẽ cung cấp cho bạn đồng thời cao mà không có chi phí chủ đề. Điều này không phải luôn luôn như vậy nhưng mã của bạn thực sự là con áp phích cho việc sử dụng gevent. Dưới đây là một ví dụ:

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

hoàn toàn không khoa học nhưng trên máy tính của tôi mã trên chạy 9x nhanh hơn tương đương với ren của nó.

Tôi hy vọng điều này sẽ hữu ích.

+1

gevent không cung cấp cho bạn bất kỳ sự tương đương nào. –

+0

Phải, không có tính song song tính toán nhưng câu hỏi ban đầu không thay đổi thuật toán fib được chọn mà thay vào đó, đề xuất một mô hình phổ biến để cải thiện nó. –

+0

Không cần thay đổi thuật toán: ví dụ đã chia tách công việc thành các tác vụ phụ độc lập. Tất cả những gì cần thiết là một chất nền thực sự thực thi các tác vụ song song (tức là, không phải là một giải pháp đồng thời như gevent). –

0

1. Tôi thiếu gì ở đây; tại sao một Pool không được chia sẻ giữa các quá trình?

Bạn thường không thể chia sẻ chuỗi hệ điều hành giữa các quy trình, bất kể ngôn ngữ.

Bạn có thể sắp xếp để chia sẻ quyền truy cập vào trình quản lý hồ bơi với quy trình công nhân, nhưng đó có thể không phải là giải pháp tốt cho bất kỳ vấn đề nào; xem bên dưới.

2. Mô hình thực hiện song song lồng nhau trong Python là gì? Nếu có thể, hãy duy trì một cấu trúc đệ quy, và không giao dịch nó để lặp lại.

Điều này phụ thuộc rất nhiều vào dữ liệu của bạn.

Trên CPython, câu trả lời chung là sử dụng cấu trúc dữ liệu thực hiện các thao tác song song hiệu quả. Một ví dụ điển hình là loại mảng tối ưu hóa của NumPy: here là một ví dụ về cách sử dụng chúng để tách một hoạt động mảng lớn trên nhiều lõi bộ xử lý. Chức năng Fibonacci được thực hiện bằng cách sử dụng tính năng chặn đệ quy là một sự phù hợp đặc biệt cho mọi cách tiếp cận dựa trên công nhân, mặc dù: fib (N) sẽ dành phần lớn thời gian của mình để buộc N công nhân làm gì ngoài chờ đợi các công nhân khác. Có nhiều cách khác để tiếp cận hàm Fibonacci cụ thể, (ví dụ: sử dụng CPS để loại bỏ việc chặn và điền số lượng công nhân không đổi), nhưng có lẽ tốt hơn để quyết định chiến lược của bạn dựa trên các vấn đề thực tế bạn sẽ giải quyết, thay vì ví dụ như thế này.