2011-11-04 52 views
8

Tôi cố gắng để quấn một mảnh C++ mã vào python lib sử dụng Boost.Python, tuy nhiên, tôi phát hiện ra rằng nhiều trường hợp không thể chạy cùng một lúc:boost.python không hỗ trợ tính song song?

mã (C++):

class Foo{ 
public: 
    Foo(){} 
    void run(){ 
     int seconds = 2; 
     clock_t endwait; 
     endwait = clock() + seconds * CLOCKS_PER_SEC ; 
     while (clock() < endwait) {} 
    } 

}; 

BOOST_PYTHON_MODULE(run_test) 
{ 
    using namespace boost::python; 

    class_<Foo>("test", init<>()) 
     .def("run", &Foo::run) 
     ; 

} 

đó là biên dịch sử dụng CMake (CMake):

add_library(run_test SHARED run_test.cpp) 
target_link_libraries(run_test boost_python python2.7) 

và thử nghiệm với đoạn mã sau (Python):

class Dos(threading.Thread): 
    def run(self): 
     printl('performing DoS attack') 

     proc = test() 
     proc.run() 

for i in range(5): 
    t = Dos() 
    t.start() 

Đầu ra cho biết mã được song song theo một cách rất kỳ lạ. Mỗi chuỗi chỉ mất 2 giây và 4 chuỗi nên chạy đồng thời trên máy lõi tứ của tôi:

[2011-11-04 13:57:01] performing DoS attack 
[2011-11-04 13:57:01] performing DoS attack 
[2011-11-04 13:57:05] performing DoS attack 
[2011-11-04 13:57:05] performing DoS attack 
[2011-11-04 13:57:09] performing DoS attack 

cảm ơn sự giúp đỡ của bạn!

+9

Vâng , điều này chắc chắn trông giống như một ứng dụng hợp pháp ...;) – larsmoa

+0

Điều này sẽ dễ dàng hơn để đọc nếu bạn chỉ ra mã nào là python và đó là C++. Tôi đã tìm ra, nhưng phải mất một chút thời gian. –

Trả lời

16

Những gì bạn đang chạy vào là Khóa thông dịch toàn cầu python. GIL chỉ cho phép một luồng tại một thời điểm để chạy trong trình thông dịch python.

Một trong những lợi thế của Boost.Python là bạn có thể giải phóng GIL, thực hiện công cụ C++ và sau đó lấy lại khi bạn hoàn tất. Đây cũng là một trách nhiệm tuy nhiên. Python thường giải phóng GIL theo các khoảng thời gian đều đặn, để cho các luồng khác có cơ hội chạy. Nếu bạn đang ở trong C++, đây là công việc của bạn. Nếu bạn đi số crunch trong 2 giờ trong khi giữ GIL, bạn sẽ đóng băng toàn bộ thông dịch viên.

Điều này có thể dễ dàng sửa chữa với một chút RAII ngược lại:

class releaseGIL{ 
public: 
    inline releaseGIL(){ 
     save_state = PyEval_SaveThread(); 
    } 

    inline ~releaseGIL(){ 
     PyEval_RestoreThread(save_state); 
    } 
private: 
    PyThreadState *save_state; 
}; 

Bây giờ bạn có thể thay đổi mã của bạn như sau:

class Foo{ 
public: 
    Foo(){} 
    void run(){ 
     { 
      releaseGIL unlock = releaseGIL(); 
      int seconds = 2; 
      clock_t endwait; 
      endwait = clock() + seconds * CLOCKS_PER_SEC ; 
      while (clock() < endwait) {} 
     } 
    } 
}; 

Điều rất quan trọng cần lưu ý rằng bạn PHẢI KHÔNG chạm bất kỳ mã python nào, hoặc dữ liệu python hoặc gọi tới trình thông dịch khi không giữ GIL. Điều này sẽ làm cho thông dịch viên của bạn bị hỏng.

Bạn cũng có thể thực hiện theo cách khác. Một chủ đề không hiện đang giữ GIL có thể có được nó, và thực hiện cuộc gọi đến python. Đây có thể là một luồng đã phát hành GIL trước đó, hoặc một chuỗi được bắt đầu bằng C++ và không bao giờ có GIL. Đây là lớp RAII cho rằng:

class AcquireGIL 
{ 
public: 
    inline AcquireGIL(){ 
     state = PyGILState_Ensure(); 
    } 

    inline ~AcquireGIL(){ 
     PyGILState_Release(state); 
    } 
private: 
    PyGILState_STATE state; 
}; 

Cách sử dụng còn lại để tập thể dục cho học sinh.

lưu ý bổ sung (Tôi luôn luôn quên đề cập đến điều này):

Nếu bạn đang đi để được can thiệp vào GIL trong C++ định nghĩa module của bạn cần để bắt đầu với mã này:

BOOST_PYTHON_MODULE(ModuleName) 
{ 
    PyEval_InitThreads(); 

    ... 
} 
+0

Cảm ơn rất nhiều! Nó giải quyết vấn đề! – guinny

+0

Có, quan trọng là bạn giải phóng GIL nếu bạn có mã python gọi vào C++, vì bất kỳ chủ đề nào khác có thể cần phải gọi vào python sẽ không hoạt động nếu không. –

+0

@fireant: cảm ơn vì đã sửa mã của tôi. –