[英]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,1
, managed 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.