2

的ASYNCIO docs讀:考慮到GIL,asyncio怎麼可能不是線程安全的?

大多數ASYNCIO對象不是線程安全的。如果您訪問事件循環外部的對象,則只應該擔心。

有人能解釋這或給的ASYNCIO的濫用是如何導致在線程之間共享的對象不同步寫個例子嗎?我認爲GIL意味着一次只有一個線程可以運行解釋器,因此在解釋器中發生的事件(如讀取和寫入Python對象)在線程之間被簡單地同步。

上面引用中的第二句聽起來像是一個線索,但我不知道該怎麼做。

我想通過釋放GIL並決定寫入Python對象總是會造成嚴重的後果,但不是特定於asyncio,所以我不認爲這就是這裏的文檔所指的。

這可能是asyncio PEPs保留某些asyncio對象的選項不是線程安全的問題,儘管目前在CPython中的實現恰好是線程安全的嗎?

+2

某些操作需要多個指令進行同步,其中的Python之間可以通過不同的線程來解釋。 GIL永遠不會*同步Python程序,與asyncio無關。 - 它只是確保Python對象在C級別上是線程安全的,而不是在Python級別上。 –

回答

1

實際上,不是,每個線程都是這個解釋器的新線程。

它是由OS,而不是內部管理只是Python的虛擬機中的Python代碼線程管理一個真正的線程。

需要GIL來防止非常基於OS的線程搞亂Python對象。

想象一個CPU上的一個線程和另一個CPU上的另一個線程。純平行線程,用匯編寫成。兩者同時嘗試更改註冊表值。根本不可取的情況。訪問相同內存位置的彙編指令最終會擾亂什麼地點以及何時移動。最後,這種行爲的結果可能很容易導致分段錯誤。那麼,如果我們用C語言編寫,C就控制這個部分,所以這不會發生在C代碼中。 GIL對C級別的Python代碼也一樣。因此,實現Python對象的代碼在更改它們時不會失去其原子性。想象一下,一個線程將一個值插入到另一個線程中正在向下移動的列表中,因爲另一個線程從中移除了一些元素。沒有GIL就會崩潰。

GIL對線程內的代碼的原子性沒有任何影響。它僅用於內部內存管理。

即使你有線程安全的對象,如雙端隊列(),如果你是在一次就可以了,無需額外的鎖做一個以上的操作,就可以得到在兩者之間插入另一個線程導致。和哎呀,問題發生!

咱們說一個線程需要的對象從堆棧,檢查一下東西,如果條件是正確刪除。

stack = [2,3,4,5,6,7,8] 
def thread1(): 
    while 1: 
     v = stack[0] 
     sleep(0.001) 
     if v%2==0: del stack[0] 
     sleep(0.001) 

當然,這是愚蠢的,並應與stack.pop(0)來進行,以避免這種情況。但這是一個例子。

然後讓另一個線程將每個0添加到堆棧。002秒:

def thread2(): 
    while 1: 
     stack.insert(0, stack[-1]+1) 
     sleep(0.002) 

現在,如果你這樣做:

thread(thread2,()) 
sleep(1) 
thread(thread1,()) 

會有一個時刻,儘管這不太可能,其中線程2()嘗試線程1之間恰好疊加了新的項目()的檢索和刪除。因此,thread1()將刪除一個新添加的項目,而不是正在檢查的項目。結果不符合我們的意願。所以,GIL不會控制我們在我們的線程中做什麼,只是線程正在做什麼 - 對於更基本的意義上的其他線程。

想象一下,你寫了一個服務器來購買一些事件的票。兩個用戶連接並嘗試同時購買同一張票。如果你不小心,用戶可能會結束坐在另一個之上。

線程安全的對象是執行操作的對象,它不允許直到第一個完成的另一個動作發生。例如,如果您在一個線程中迭代deque(),並在另一個線程的中間另一個線程嘗試追加某些內容,append()將會阻塞,直到第一個線程完成迭代爲止。這是線程安全的。

+0

'GIL不控制我們在我們的線程中正在做什麼,只是線程正在對其他線程做什麼 - 這條線是純金。 – xyres

相關問題