簡體   English   中英

異步代碼、共享變量、線程池線程和線程安全

[英]Asynchronous code, shared variables, thread-pool threads and thread safety

當我使用 async/await 編寫異步代碼時,通常使用ConfigureAwait(false)來避免捕獲上下文,我的代碼在每個await之后從一個線程池線程跳轉到下一個線程。 這引起了對線程安全的擔憂。 這段代碼安全嗎?

static async Task Main()
{
    int count = 0;
    for (int i = 0; i < 1_000_000; i++)
    {
        Interlocked.Increment(ref count);
        await Task.Yield();
    }
    Console.WriteLine(count == 1_000_000 ? "OK" : "Error");
}

變量i不受保護,可被多個線程池線程*訪問。 盡管訪問模式是非並發的,但理論上每個線程應該可以增加本地緩存的i值,從而導致超過 1,000,000 次迭代。 雖然我無法在實踐中產生這種情況。 上面的代碼總是在我的機器上打印 OK。 這是否意味着代碼是線程安全的? 或者我應該使用lock同步對i變量的訪問?

(* 根據我的測試,平均每 2 次迭代發生一次線程切換)

線程安全的問題在於讀/寫 memory。 即使這可以在不同的線程上繼續,這里也不會並發執行。

我相信 Stephen Toub 的這篇文章可以闡明這一點。 特別是,這是關於上下文切換期間發生的事情的相關段落:

每當代碼等待一個等待者說它尚未完成的等待對象時(即等待者的 IsCompleted 返回 false),該方法需要暫停,並且它將通過等待者的延續來恢復。 這是我之前提到的那些異步點之一,因此,ExecutionContext 需要從發出等待的代碼流向延續委托的執行。 這是由框架自動處理的。 當異步方法即將掛起時,基礎結構會捕獲一個 ExecutionContext。 傳遞給等待者的委托具有對此 ExecutionContext 實例的引用,並將在恢復該方法時使用它。 這就是使 ExecutionContext 表示的重要“環境”信息能夠流經等待的原因。

值得注意的是YieldAwaitable Task.Yield()返回的 YieldAwaitable 總是返回false

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM