============================ 12.9 Python的全局é”问题 ============================ ---------- 问题 ---------- ä½ å·²ç»å¬è¯´è¿‡å…¨å±€è§£é‡Šå™¨é”GIL,担心它会影å“到多线程程åºçš„æ‰§è¡Œæ€§èƒ½ã€‚ ---------- 解决方案 ---------- 尽管Python完全支æŒå¤šçº¿ç¨‹ç¼–程, 但是解释器的Cè¯è¨€å®žçŽ°éƒ¨åˆ†åœ¨å®Œå…¨å¹¶è¡Œæ‰§è¡Œæ—¶å¹¶ä¸æ˜¯çº¿ç¨‹å®‰å…¨çš„。 实际上,解释器被一个全局解释器é”ä¿æŠ¤ç€ï¼Œå®ƒç¡®ä¿ä»»ä½•æ—¶å€™éƒ½åªæœ‰ä¸€ä¸ªPython线程执行。 GIL最大的问题就是Python的多线程程åºå¹¶ä¸èƒ½åˆ©ç”¨å¤šæ ¸CPU的优势 (比如一个使用了多个线程的计算密集型程åºåªä¼šåœ¨ä¸€ä¸ªå•CPU上é¢è¿è¡Œï¼‰ã€‚ 在讨论普通的GIL之å‰ï¼Œæœ‰ä¸€ç‚¹è¦å¼ºè°ƒçš„æ˜¯GILåªä¼šå½±å“到那些严é‡ä¾èµ–CPU的程åºï¼ˆæ¯”如计算型的)。 å¦‚æžœä½ çš„ç¨‹åºå¤§éƒ¨åˆ†åªä¼šæ¶‰åŠåˆ°I/O,比如网络交互,那么使用多线程就很åˆé€‚, å› ä¸ºå®ƒä»¬å¤§éƒ¨åˆ†æ—¶é—´éƒ½åœ¨ç‰å¾…ã€‚å®žé™…ä¸Šï¼Œä½ å®Œå…¨å¯ä»¥æ”¾å¿ƒçš„åˆ›å»ºå‡ åƒä¸ªPython线程, 现代æ“作系统è¿è¡Œè¿™ä¹ˆå¤šçº¿ç¨‹æ²¡æœ‰ä»»ä½•åŽ‹åŠ›ï¼Œæ²¡å•¥å¯æ‹…心的。 而对于ä¾èµ–CPU的程åºï¼Œä½ 需è¦å¼„清楚执行的计算的特点。 ä¾‹å¦‚ï¼Œä¼˜åŒ–åº•å±‚ç®—æ³•è¦æ¯”使用多线程è¿è¡Œå¿«å¾—多。 类似的,由于Pythonæ˜¯è§£é‡Šæ‰§è¡Œçš„ï¼Œå¦‚æžœä½ å°†é‚£äº›æ€§èƒ½ç“¶é¢ˆä»£ç 移到一个Cè¯è¨€æ‰©å±•模å—ä¸ï¼Œ 速度也会æå‡çš„å¾ˆå¿«ã€‚å¦‚æžœä½ è¦æ“作数组,那么使用NumPyè¿™æ ·çš„æ‰©å±•ä¼šéžå¸¸çš„高效。 最åŽï¼Œä½ 还å¯ä»¥è€ƒè™‘下其他å¯é€‰å®žçŽ°æ–¹æ¡ˆï¼Œæ¯”å¦‚PyPy,它通过一个JIT编译器æ¥ä¼˜åŒ–执行效率 (ä¸è¿‡åœ¨å†™è¿™æœ¬ä¹¦çš„æ—¶å€™å®ƒè¿˜ä¸èƒ½æ”¯æŒPython 3)。 è¿˜æœ‰ä¸€ç‚¹è¦æ³¨æ„çš„æ˜¯ï¼Œçº¿ç¨‹ä¸æ˜¯ä¸“门用æ¥ä¼˜åŒ–性能的。 一个CPUä¾èµ–型程åºå¯èƒ½ä¼šä½¿ç”¨çº¿ç¨‹æ¥ç®¡ç†ä¸€ä¸ªå›¾å½¢ç”¨æˆ·ç•Œé¢ã€ä¸€ä¸ªç½‘络连接或其他æœåŠ¡ã€‚ 这时候,GILä¼šäº§ç”Ÿä¸€äº›é—®é¢˜ï¼Œå› ä¸ºå¦‚æžœä¸€ä¸ªçº¿ç¨‹é•¿æœŸæŒæœ‰GILçš„è¯ä¼šå¯¼è‡´å…¶ä»–éžCPU型线程一直ç‰å¾…。 事实上,一个写的ä¸å¥½çš„Cè¯è¨€æ‰©å±•ä¼šå¯¼è‡´è¿™ä¸ªé—®é¢˜æ›´åŠ ä¸¥é‡ï¼Œ 尽管代ç 的计算部分会比之å‰è¿è¡Œçš„æ›´å¿«äº›ã€‚ 说了这么多,现在想说的是我们有两ç§ç–ç•¥æ¥è§£å†³GIL的缺点。 é¦–å…ˆï¼Œå¦‚æžœä½ å®Œå…¨å·¥ä½œäºŽPython环境ä¸ï¼Œä½ å¯ä»¥ä½¿ç”¨ ``multiprocessing`` æ¨¡å—æ¥åˆ›å»ºä¸€ä¸ªè¿›ç¨‹æ± , å¹¶åƒååŒå¤„ç†å™¨ä¸€æ ·çš„使用它。例如,å‡å¦‚ä½ æœ‰å¦‚ä¸‹çš„çº¿ç¨‹ä»£ç : .. code-block:: python # Performs a large calculation (CPU bound) def some_work(args): ... return result # A thread that calls the above function def some_thread(): while True: ... r = some_work(args) ... 修改代ç ï¼Œä½¿ç”¨è¿›ç¨‹æ± ï¼š .. code-block:: python # Processing pool (see below for initiazation) pool = None # Performs a large calculation (CPU bound) def some_work(args): ... return result # A thread that calls the above function def some_thread(): while True: ... r = pool.apply(some_work, (args)) ... # Initiaze the pool if __name__ == '__main__': import multiprocessing pool = multiprocessing.Pool() è¿™ä¸ªé€šè¿‡ä½¿ç”¨ä¸€ä¸ªæŠ€å·§åˆ©ç”¨è¿›ç¨‹æ± è§£å†³äº†GIL的问题。 å½“ä¸€ä¸ªçº¿ç¨‹æƒ³è¦æ‰§è¡ŒCPU密集型工作时,会将任务å‘ç»™è¿›ç¨‹æ± ã€‚ ç„¶åŽè¿›ç¨‹æ± 会在å¦å¤–一个进程ä¸å¯åŠ¨ä¸€ä¸ªå•独的Python解释器æ¥å·¥ä½œã€‚ 当线程ç‰å¾…结果的时候会释放GIL。 并且,由于计算任务在å•ç‹¬è§£é‡Šå™¨ä¸æ‰§è¡Œï¼Œé‚£ä¹ˆå°±ä¸ä¼šå—é™äºŽGIL了。 åœ¨ä¸€ä¸ªå¤šæ ¸ç³»ç»Ÿä¸Šé¢ï¼Œä½ 会å‘现这个技术å¯ä»¥è®©ä½ 很好的利用多CPU的优势。 å¦å¤–一个解决GILçš„ç–略是使用C扩展编程技术。 ä¸»è¦æ€æƒ³æ˜¯å°†è®¡ç®—密集型任务转移给C,跟Python独立,在工作的时候在C代ç ä¸é‡Šæ”¾GIL。 è¿™å¯ä»¥é€šè¿‡åœ¨C代ç 䏿’入下é¢è¿™æ ·çš„ç‰¹æ®Šå®æ¥å®Œæˆï¼š :: #include "Python.h" ... PyObject *pyfunc(PyObject *self, PyObject *args) { ... Py_BEGIN_ALLOW_THREADS // Threaded C code ... Py_END_ALLOW_THREADS ... } å¦‚æžœä½ ä½¿ç”¨å…¶ä»–å·¥å…·è®¿é—®Cè¯è¨€ï¼Œæ¯”如对于Cythonçš„ctypesåº“ï¼Œä½ ä¸éœ€è¦åšä»»ä½•事。 例如,ctypes在调用C时会自动释放GIL。 ---------- 讨论 ---------- 许多程åºå‘˜åœ¨é¢å¯¹çº¿ç¨‹æ€§èƒ½é—®é¢˜çš„æ—¶å€™ï¼Œé©¬ä¸Šå°±ä¼šæ€ªç½ªGIL,什么都是它的问题。 å…¶å®žè¿™æ ·å太ä¸åŽšé“也太天真了点。 作为一个真实的例å,在多线程的网络编程ä¸ç¥žç§˜çš„ ``stalls`` å¯èƒ½æ˜¯å› ä¸ºå…¶ä»–åŽŸå› æ¯”å¦‚ä¸€ä¸ªDNS查找延时,而跟GILæ¯«æ— å…³ç³»ã€‚ 最åŽä½ 真的需è¦å…ˆåŽ»æžæ‡‚ä½ çš„ä»£ç æ˜¯å¦çœŸçš„被GILå½±å“到。 åŒæ—¶è¿˜è¦æ˜Žç™½GIL大部分都应该åªå…³æ³¨CPU的处ç†è€Œä¸æ˜¯I/O. å¦‚æžœä½ å‡†å¤‡ä½¿ç”¨ä¸€ä¸ªå¤„ç†å™¨æ± ,注æ„çš„æ˜¯è¿™æ ·åšæ¶‰åŠåˆ°æ•°æ®åºåˆ—化和在ä¸åŒPython解释器通信。 被执行的æ“ä½œéœ€è¦æ”¾åœ¨ä¸€ä¸ªé€šè¿‡defè¯å¥å®šä¹‰çš„Python函数ä¸ï¼Œä¸èƒ½æ˜¯lambdaã€é—包å¯è°ƒç”¨å®žä¾‹ç‰ï¼Œ å¹¶ä¸”å‡½æ•°å‚æ•°å’Œè¿”回值必须è¦å…¼å®¹pickle。 åŒæ ·ï¼Œè¦æ‰§è¡Œçš„任务é‡å¿…须足够大以弥补é¢å¤–的通信开销。 å¦å¤–一个难点是当混åˆä½¿ç”¨çº¿ç¨‹å’Œè¿›ç¨‹æ± çš„æ—¶å€™ä¼šè®©ä½ å¾ˆå¤´ç–¼ã€‚ å¦‚æžœä½ è¦åŒæ—¶ä½¿ç”¨ä¸¤è€…,最好在程åºå¯åŠ¨æ—¶ï¼Œåˆ›å»ºä»»ä½•çº¿ç¨‹ä¹‹å‰å…ˆåˆ›å»ºä¸€ä¸ªå•ä¾‹çš„è¿›ç¨‹æ± ã€‚ ç„¶åŽçº¿ç¨‹ä½¿ç”¨åŒæ ·çš„è¿›ç¨‹æ± æ¥è¿›è¡Œå®ƒä»¬çš„计算密集型工作。 C扩展最é‡è¦çš„ç‰¹å¾æ˜¯å®ƒä»¬å’ŒPythonè§£é‡Šå™¨æ˜¯ä¿æŒç‹¬ç«‹çš„。 ä¹Ÿå°±æ˜¯è¯´ï¼Œå¦‚æžœä½ å‡†å¤‡å°†Pythonä¸çš„任务分é…到Cä¸åŽ»æ‰§è¡Œï¼Œ ä½ éœ€è¦ç¡®ä¿C代ç çš„æ“作跟Pythonä¿æŒç‹¬ç«‹ï¼Œ 这就æ„味ç€ä¸è¦ä½¿ç”¨Pythonæ•°æ®ç»“构以åŠä¸è¦è°ƒç”¨Pythonçš„C API。 å¦å¤–ä¸€ä¸ªå°±æ˜¯ä½ è¦ç¡®ä¿C扩展所åšçš„å·¥ä½œæ˜¯è¶³å¤Ÿçš„ï¼Œå€¼å¾—ä½ è¿™æ ·åšã€‚ 也就是说C扩展担负起了大é‡çš„è®¡ç®—ä»»åŠ¡ï¼Œè€Œä¸æ˜¯å°‘æ•°å‡ ä¸ªè®¡ç®—ã€‚ 这些解决GIL的方案并ä¸èƒ½é€‚用于所有问题。 例如,æŸäº›ç±»åž‹çš„应用程åºå¦‚果被分解为多个进程处ç†çš„è¯å¹¶ä¸èƒ½å¾ˆå¥½çš„工作, 也ä¸èƒ½å°†å®ƒçš„éƒ¨åˆ†ä»£ç æ”¹æˆCè¯è¨€æ‰§è¡Œã€‚ 对于这些应用程åºï¼Œä½ å°±è¦è‡ªå·±éœ€æ±‚解决方案了 (比如多进程访问共享内å˜åŒºï¼Œå¤šè§£æžå™¨è¿è¡ŒäºŽåŒä¸€ä¸ªè¿›ç¨‹ç‰ï¼‰ã€‚ æˆ–è€…ï¼Œä½ è¿˜å¯ä»¥è€ƒè™‘下其他的解释器实现,比如PyPy。 了解更多关于在C扩展ä¸é‡Šæ”¾GIL,请å‚考15.7å’Œ15.10å°èŠ‚ã€‚