Python中關於Thread的一點小知識
來自專欄編程小記2 人贊了文章
最近在實現了一個對sqlite3進行簡單封裝的非同步庫aiosqlite,讓其支持非同步的方式調用。因為是python2.7,標準庫中沒有原生的類似asyncio的模塊,所以依賴第三方tornado庫。
由於sqlite3本身查詢數據文件的操作是阻塞的,要想實現非同步調用,就不得不通過多線程的方式,在執行查詢語句的時候通過多線程操作,從而達到偽非同步。
使用多線程的過程中,剛好跟同事聊了幾句關於多線程的問題,儘管可能是一些基礎的知識,但是平時由於沒怎麼考慮過這塊的問題,所以覺得記錄一下也好。
以下代碼皆基於python2.7版本
Python中當開啟的線程任務結束後,線程是否被銷毀了?
關於這個問題我理所當然的認為是銷毀了,但是同事的看法是:線程執行完成任務後退出,但實際並沒有銷毀,python本身也沒有銷毀線程的功能,想銷毀線程的話要通過操作系統本身提供的介面。
通常我們在使用Thread時,都是start()開始線程任務,最終join()結束線程,所以看了下cpython中threading.Thread的源碼,關於join()方法的說明中,也並未明確指出線程銷毀的問題。
最終還是得通過實踐出真知。在CentOS 7 x64系統中寫了點測試代碼簡單驗證一下。關於進程的線程數可以通過cat /proc/<pid>/status|grep Thread查看。
import timefrom threading import Threaddef t1(): print(Thread 1 start) time.sleep(10) print(Thread 1 done)def t2(): print(Thread 2 start) time.sleep(30) print(Thread 2 done)t1 = Thread(target=t1, daemon=True)t2 = Thread(target=t2, daemon=True)for task in (t1, t2): task.start()for task in (t1, t2): task.join()
開始執行後查看到的Thread數是3,當Thread 1結束後再次查看發現Thread 2數變為2。可見,線程任務結束後,線程銷毀了的。
線程任務中,如果其中一個線程阻塞,其他的線程是否還正常運行?
關於這個問題,就顯得我有些愚蠢了。由於滿腦子想的都是`GIL`,同一時間只可能有一個線程在跑,那如果這個線程阻塞了,其他的線程肯定也跑不下去了,所以就認為一個線程阻塞,其他的線程肯定也阻塞了。同事的看法則是,肯定不會阻塞,不然還叫什麼多線程。
實際通過demo測試後發現,一個線程阻塞並不會對其他線程造成影響。由於對GIL一知半解,所以造成這種錯誤認知。看了下GIL的資料後了解到,Python的多線程是調用系統多線程介面,GIL只是一把全局鎖,一個線程執行時獲取到GIL後,執行過程中如果遇到IO阻塞,會釋放掉GIL,這樣輪到其他的線程執行。所以,不會存在一個線程阻塞,其他線程也跟著阻塞的問題。
這真是個低級的錯誤。。。
執行多線程任務時,如果其中一個線程中執行了`sys.exit()`整個進程是否會退出?
同事的看法是會退出,我和另一個同事則不太敢肯定。demo代碼如下
import sysimport timefrom threading import Threaddef t1(): print(Thread 1 start) sys.exit() print(Thread 1 done)def t2(): k = 10 if k: print(Thread 2 is running) time.sleep(3) k -= 1t1 = Thread(target=t1, daemon=True)t2 = Thread(target=t2, daemon=True)for task in (t1, t2): task.start()for task in (t1, t2): task.join()
結果是,直到t2運行結束後進程才會退出,t1中的sys.exit()並不會造成整個進程的退出。
看源碼sysmodule.c
static PyObject *sys_exit(PyObject *self, PyObject *args){ PyObject *exit_code = 0; if (!PyArg_UnpackTuple(args, "exit", 0, 1, &exit_code)) return NULL; /* Raise SystemExit so callers may catch it or clean up. */ PyErr_SetObject(PyExc_SystemExit, exit_code); return NULL;}
可以看到,返回值總是NULL,但在exit_code不為0時,會set一個PyExc_SystemExit。全局搜索一下PyExc_SystemExit。
在_threadmodule.c中可以找到
PyDoc_STRVAR(start_new_doc,"start_new_thread(function, args[, kwargs])
(start_new() is an obsolete synonym)
Start a new thread and return its identifier. The thread will call the
function with positional arguments from the tuple args and keyword arguments
taken from the optional dictionary kwargs. The thread exits when the
function returns; the return value is ignored. The thread will also exit
when the function raises an unhandled exception; a stack trace will be
printed unless the exception is SystemExit.
");static PyObject *thread_PyThread_exit_thread(PyObject *self){ PyErr_SetNone(PyExc_SystemExit); return NULL;}
其實線程任務正常退出也會set一個`PyExc_SystemExit`,所以在線程中`sys.exit()`並不會讓整個進程退出。
以上僅為個人見解,如有認知錯誤,歡迎指正,謝謝。
參考:
Python的GIL是什麼鬼,多線程性能究竟如何
Understanding the Python GIL
Python/sysmodule.c
Modules/threadmodule.c
推薦閱讀:
※為什麼 Nginx 已經這麼成熟,Python 還有各種如 web.py 等 web 框架?
※python 能否print到console固定一行?
※Python與Echarts的結合——pyecharts
※spyder 如何添加和安裝其他的包?
※參加數學建模,使用python會不會比matlab有劣勢?