簡體   English   中英

任務並行庫(TPL)中的線程同步

[英]Thread synchronization in the Task Parallel Library (TPL)

我正在學習TPL並堅持不懈。 這只是為了學習目的,我希望人們能指導我正確的方向。

我只想要一個線程一次訪問變量sum ,這樣它就不會被覆蓋。

我的代碼如下。

using System;
using System.Threading.Tasks;


class ThreadTest
{
    private Object thisLock = new Object();
    static int sum = 0;
    public void RunMe()
    {
        lock (thisLock)
        {
            sum = sum + 1;
        }
    }

    static void Main()
    {
        ThreadTest b = new ThreadTest();
        Task t1 = new Task(()=>b.RunMe());
        Task t2= new Task(() => b.RunMe());
        t1.Start();
        t2.Start();
        Task.WaitAll(t1, t2);
        Console.WriteLine(sum.ToString());
        Console.ReadLine();
    }
}

問題 -我在這段代碼中是對的?

問題 -我沒有鎖就可以做到,因為我在某個地方讀到它應該避免,因為它不允許任務彼此通信。我已經看到了一些async和await的例子,但我使用的是.Net 4.0。

謝謝

我在這段代碼中是對的

明智的實施是的,但理解明智不,因為你在嘗試實現邏輯時混淆了新舊世界,讓我試着詳細解釋。

  • Task t1 = new Task(()=>b.RunMe()); 並不意味着每次都是Thread API的新線程
  • Task API將調用線程池線程,因此很可能兩個Task對象t1,t2在大多數情況下在同一個線程上執行,用於短運行邏輯,並且在嘗試更新時,從不需要顯式lock的競爭條件shared object
  • 防止Sum object出現競爭條件的更好方法是Interlocked.Increment(ref sum) ,這是一種對primitive types進行基本操作的線程安全機制
  • 對於你正在做的更好的API操作類型是Parallel.For ,而不是創建一個單獨的Task ,好處是你可以用最少的努力運行任意數量的這種increment操作,而不是創建一個單獨的任務,它自動阻止主線程,因此您的代碼應如下所示:

     using System; using System.Threading.Tasks; class ThreadTest { public static int sum; } static void Main() { Parallel.For(0, 1, i => { // Some thread instrumentation Console.WriteLine("i = {0}, thread = {1}", i, Thread.CurrentThread.ManagedThreadId); Interlocked.Increment(ref ThreadTest.sum); }); Console.WriteLine(ThreadTest.sum.ToString()); Console.ReadLine(); } } 
  • 在使用Thread檢測時,您會發現兩個循環0,1managed thread id相同的可能性,從而避免了之前建議的線程安全性需求

答案1:

這是您發布的代碼的線程安全。

但是,正如Sam指出的那樣,在一般情況下,這當前不是線程安全的,因為增加的字段是靜態的,但鎖定對象不是靜態的。

這意味着可以在兩個單獨的線程上創建兩個獨立的ThreadTest實例,然后可以從這些線程調用RunMe() ,並且因為每個實例都有一個單獨的鎖定對象,所以鎖定不起作用。

這里的解決方案是使鎖定對象也是靜態的。

答案2:

您可以使用Interlocked.Increment()沒有顯式鎖定的情況下執行此操作:

public void RunMe()
{
    Interlocked.Increment(ref sum);
}

現在到了這一點,因為我添加了一些關於downvotes的不滿意的評論,沒有理由:)。

您的方案正在運行,並且是經典的多線程使用。 經典因為使用系統鎖,即鎖實際上是操作系統的WinAPI鎖,所以為了進行同步,代碼必須設法響應操作系統並返回,當然,由於某些爭用,可能會因切換線程而浪費一些時間。特別是如果您在運行給定任務的每個線程中多次訪問RunMe或者創建更多並發任務,感謝2。

請嘗試查看原子操作。 對於你的場景,它可以很好地工作,Interlocked.Increment(ref sum)。 從那里你必須限制自己直接訪問總和,但這不是問題,因為Increment方法返回最新結果。

另一種選擇是使用SpinLock,即如果您的操作非常快。 永遠不會打開某些東西,比如Console.WriteLine或任何其他系統操作或長時間運行的計算等。

這里有許多例子:

using System;
using System.Threading;
using System.Threading.Tasks;

class ThreadTest
{
    /// <summary>  DO NOT TOUCH ME DIRECTLY  </summary>
    private static int sum/* = 0 zero is default*/;
    private static int Add(int add) => Interlocked.Add(ref sum, add);
    private static int Increment() => Interlocked.Increment(ref sum);
    private static int Latest() => Interlocked.Add(ref sum, 0);
    private static void RunMe() => Increment();

    static void Main()
    {
        Task t1 = new Task(RunMe);
        Task t2 = new Task(RunMe);
        t1.Start();
        t2.Start();
        Task.WaitAll(t1, t2);
        Console.WriteLine(Latest().ToString());
        Console.ReadLine();
    }
}

暫無
暫無

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

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