![](/img/trans.png)
[英]Which is the proper way to use locking inside of Parallel.ForEach?
[英]How to do proper Parallel.ForEach, locking and progress reporting
我正在嘗試實現Parallel.ForEach
模式並跟蹤進度,但我遺漏了一些有關鎖定的內容。 以下示例在threadCount = 1
時計數到1000,但在threadCount
> 1時不threadCount
。正確的方法是什么?
class Program
{
static void Main()
{
var progress = new Progress();
var ids = Enumerable.Range(1, 10000);
var threadCount = 2;
Parallel.ForEach(ids, new ParallelOptions { MaxDegreeOfParallelism = threadCount }, id => { progress.CurrentCount++; });
Console.WriteLine("Threads: {0}, Count: {1}", threadCount, progress.CurrentCount);
Console.ReadKey();
}
}
internal class Progress
{
private Object _lock = new Object();
private int _currentCount;
public int CurrentCount
{
get
{
lock (_lock)
{
return _currentCount;
}
}
set
{
lock (_lock)
{
_currentCount = value;
}
}
}
}
從多個線程(共享count
變量)調用count++
類的常見問題是這一系列事件可能發生:
count
的值。 count
的值。 count
。 count
。 這樣,線程A寫入的值被線程B覆蓋,因此該值實際上只增加一次。
您的代碼會在操作1,2( get
)和5,6( set
)周圍添加鎖定,但這對阻止有問題的事件序列沒有任何作用。
你需要做的是鎖定整個操作,這樣當線程A遞增值時,線程B根本無法訪問它:
lock (progressLock)
{
progress.CurrentCount++;
}
如果您知道只需要遞增,則可以在Progress
上創建一個封裝此方法的方法。
老問題,但我認為有更好的答案。
您可以使用Interlocked.Increment(ref progress)
報告進度,這樣您就不必擔心將寫入操作鎖定為進度。
最簡單的解決方案實際上是用字段替換屬性,並且
lock { ++progress.CurrentCount; }
(我個人更喜歡在增量后增加前體的外觀,因為“++。”的東西在我的腦海中發生沖突!但是后增量當然會起作用。)
這將具有減少開銷和爭用的額外好處,因為更新字段比調用更新字段的方法更快。
當然,將其封裝為屬性也具有優勢。 IMO,因為字段和屬性語法是相同的,當屬性自動實現或等效時,在字段上使用屬性的唯一好處是,當您有一個場景,您可能希望部署一個程序集而不必構建和部署依賴項重新組裝。 否則,您也可以使用更快的字段! 如果需要檢查值或添加副作用,您只需將字段轉換為屬性並再次構建。 因此,在許多實際情況中,使用場不會受到懲罰。
然而,我們生活在一個許多開發團隊教條操作的時代,並使用StyleCop等工具來強化他們的教條主義。 與編碼器不同,這些工具不夠聰明,無法判斷何時使用字段是可以接受的,因此“即使是StyleCop檢查也很簡單的規則”變成“將字段封裝為屬性”,“不使用公共字段”等等...
從屬性中刪除鎖定語句並修改主體:
object sync = new object();
Parallel.ForEach(ids, new ParallelOptions {MaxDegreeOfParallelism = threadCount},
id =>
{
lock(sync)
progress.CurrentCount++;
});
這里的問題是++
不是原子的 - 一個線程可以讀取並遞增讀取值的另一個線程之間的值,並存儲(現在不正確的)遞增值。 事實上,包含你的int
的屬性可能更加復雜。
例如
Thread 1 Thread 2
reads 5 .
. reads 5
. writes 6
writes 6! .
setter和getter周圍的鎖沒有幫助,因為沒有什么可以阻止lock
它們被無序調用。
通常,我建議使用Interlocked.Increment
,但你不能將它與屬性一起使用。
相反,你可以公開_lock
並讓lock
塊圍繞progress.CurrentCount++;
呼叫。
最好將任何數據庫或文件系統操作存儲在本地緩沖區變量中,而不是將其鎖定。 鎖定會降低性能。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.