- 問題:這份代碼在 3.11.3 中它居然輸出 0 ,一度以為自己寫錯(cuò)了,抱著不信邪的態(tài)度,又搞了個(gè) Python 3.9.7 的環(huán)境試了下,果然還是符合自己預(yù)期,輸出不為 0,想問下 3.11 版本中是做了什么修改嗎?
import threading
num = 0
def add():
global num
for i in range(10_000_000):
num += 1
def sub():
global num
for i in range(10_000_000):
num -= 1
if __name__ == "__main__":
add_t = threading.Thread(target=add)
sub_t = threading.Thread(target=sub)
add_t.start()
sub_t.start()
add_t.join()
sub_t.join()
print("num result : %s" % num)
- 答案:
-
首先在 Python 字節(jié)碼執(zhí)行的時(shí)候 ,GIL 并不是隨時(shí)能在任意位置中斷切換線程。只有在主動(dòng)檢測中斷的地方才可能發(fā)生線程切換。這個(gè)是大前提
-
3.10 之前的版本中,INPLACE_ADD 這個(gè) opcode 之后 GIL 會(huì)去主動(dòng)監(jiān)測中斷,所以導(dǎo)致現(xiàn)成不安全。3.10 的代碼中有一個(gè)提交,
https://github.com/python/cpython/commit/4958f5d69dd2bf86866c43491caf72f774ddec97
根據(jù) T. Wouters 的 Twitter 描述https://twitter.com/Yhg1s/status/1460935209059328000
這次提交修改了 INPLACE_ADD 之后主動(dòng)監(jiān)測中斷的操作。使得 INPLACE_ADD 之后無論如何都不會(huì)發(fā)生線程切換,因此雖然是兩個(gè) opcode ,但是確實(shí)是線程安全。 -
因?yàn)樽止?jié)碼中+=的操作是兩步 opcode 操作,且 INPLACE_ADD 之后 GIL 會(huì)主動(dòng)監(jiān)測中斷,導(dǎo)致雖然加了,但是沒有重新賦值,就切換到了別的線程上**。比如 A 線程 當(dāng)前 num=100 。+=1 之后 101 但是買沒來得及重新賦值給 num ,GIL 切換了線程,再 B 線程中 num 還是 100 ,-=之后就是 99 ,但是這個(gè)線程卻賦值給了 num ,此時(shí) num 就是 99 然后又且回了 A 線程**。結(jié)果線程將中斷時(shí)候的 101 賦值給了 num 導(dǎo)致此時(shí) num 變成了 101 就出現(xiàn)問題了文章來源:http://www.zghlxwxcb.cn/news/detail-698772.html
-
3.10 以后就不會(huì)出現(xiàn)這個(gè)問題了,因?yàn)?INPLACE_ADD 操作之后 GIL 不再會(huì)主動(dòng)檢測中斷,意味著正常情況下執(zhí)行完+=之后線程不會(huì)被切換,而是正確執(zhí)行了賦值給 num 的操作文章來源地址http://www.zghlxwxcb.cn/news/detail-698772.html
-
到了這里,關(guān)于Python 3.11 版本是對(duì)線程安全做了什么更改嗎的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!