因為 CPython 有 GIL 的緣故, 需要提升 CPU 效率時, 不會用 multi-thread, 會改用 multi-process。內建模組 multiprocessing 提供許多好東西, 實作 multi-process 簡單許多。習慣用 multiprocessing 後,有時不是 CPU bound 而是 I/O bound, 我還是用 multiprocessing。反正....不是 CPU bound 的話, 多耗 CPU 也無所謂。
之前寫 crawling 的程式,為了增加同時抓網頁的數量,就用 multiprocessing 加速 [*1]。以下是兩個例子:
- 取出台北熱門電影的 IMDB 分數 (使用 Pool)
- 取出 Google Play 相似 app 的分數分佈 (沒用 Pool, 有用 Lock。Btw, Pool 感覺沒有很方便)
從例子裡找片段程式碼來用比看文件快。這裡記錄一下相關心得:
- 若要讓不同 process 共享資料,需要用 multiprocessing 訂的物件, 有含 list, dict 等,單一物件 (如 int, float, str) 則是用 Value 指定 typecode, 如: shared_integer = multiprocessing.Value('i', 0)
- 使用 shared data 會有 race condition, 要用 Lock 保護,不然就直接用 Queue 或 Pipe。
- 還有 Event 和 Semaphore 等物件,要作比較複雜的機制時,可以拿來用。
- 有提供跨機器的 multi-processing, 有需要再來細看。
- 程式架構照 producer-consumer 的方式寫比較容易, 也就是各個 process 共用 Queue, 然後 producer 用 blocking put, consumer 用 blocking get, 另有一個 main process 監控情況, 確保程式會結束 (這大概是最麻煩的地方)。
- 注意量大的時候 put 會因 queue full 卡住,所以不能在 main process 用 blocking put。即使很容易填入 Queue 的初始資料,用另外的 process 填入初始資料比較保險, 因為在初始資料太多時,可能因 queue full 卡住不動了。不然就是先產生 consumer process 再初始化 Queue, 避免因為沒有 consumer 而在 queue full 時卡死。我偏好用另外的 process 初始化 Queue 的資料, 因為新增 process 成本不高 (只有一次), 程式邏輯比較單純。
- 雖然有 non-blocking put/get, 但是使用 non-blocking 操作, 要多記操作是否失敗, 失敗要另排時間重試, 還是新增 process (or thread) 用 blocking 操作易寫易懂。
- 各種意外都有可能發生,process 因 exception 掛掉的話,可能會讓 main process 搞錯情況而不會結束,process 執行的函式最上層要加個 try-catch 確保發生意外時能重置管理 process 的狀態,確保 main process 會結束。crawl_ratings.py 分成 _parse_url() 和 _do_parse_url() 就是要處理這個問題。
- multiprocessing 有提供 log, 可開啟輸出到 stderr, 方便追蹤各 process 情況。用法是: multiprocessing.log_to_stderr(logging.INFO)
備註
*1 遇到 I/O bound 時, 除 multi-thread, multi-process 外,還有一個選擇是用 single thread event-based 的解法。Python 有 gevent 可用。
但是 gevent 的缺點是,一但用了 gevent, 全部 I/O 都要用 gevent, 不然 main thread 就被卡住了。於是 third-party 程式也要找使用 gevent 的版本。比方說 requests 很好用,但用了 gevent 後要用 gevent 版的 requests, 用其它公司提供的 SDK, 也要自己 patch 成 gevent 版本, 所以使用前要三思。不是要作到 C10K 等級的 daemon, 還是用 multiprocessing 省事。之前作一個上線的服務是用 gevent, 為此費了一些工夫作到全盤使用 gevent。
相關資料
- 除官網詳細的資料外, PyMOTW 也有很多好資料, 之後再來細讀。
- 過去寫非同步程式的心得, 分析非同步程式的使用和實作種類。
2015-09-01 更新
懶得研究這麼多可以考慮用 prunner.py。
沒有留言:
張貼留言