我最近開始重新審視我的一些舊的多執行緒代碼,并想知道它是否安全和正確(生產中還沒有問題......)。特別是我是否正確處理物件參考?我已經閱讀了大量使用簡單原語(如整數)的示例,但與參考和任何可能的細微差別有關的示例并不多。
首先,我最近了解到物件參考分配是原子的,至少在 64 位機器上是我在這個特定應用程式中關注的全部內容。以前,我鎖定類屬性的 get/sets 以避免破壞參考,因為我沒有意識到參考分配是原子的。例如:
// Immutable collection of options for a Contact
public class ContactOptions
{
public string Email { get; }
public string PhoneNumber { get; }
}
// Sample class that implements the Options
public class Contact
{
private readonly object OptionsLock = new object();
private ContactOptions _Options;
public ContactOptions Options { get { lock(OptionsLock) { return _Options; } }
set { lock(OptionsLock) { _Options = value; } } };
}
現在我知道參考分配是原子的,我想“太好了,是時候洗掉這些丑陋且不必要的鎖了!” 然后我進一步閱讀并了解了執行緒之間的記憶體同步。現在我又回到保持鎖定狀態以確保資料在訪問時不會過時。例如,如果我訪問聯系人的選項,我想確保我始終收到分配的最新選項集。
問題:
- 如果我在這里錯了,請糾正我,但上面的代碼確實確保我在以執行緒安全的方式獲取 Options 的最新值時實作了獲取它的目標?使用此方法還有其他問題嗎?
- 我相信鎖有一些開銷(轉換為 Monitor.Enter/Exit)。我認為我可以使用 Interlocked 來獲得名義上的性能提升,但對我來說更重要的是一組更清晰的代碼。以下作業可以實作同步嗎?
private ContactOptions _Options;
public ContactOptions Options {
get { return Interlocked.CompareExchange(ref _Options, null, null); }
set { Interlocked.Exchange(ref _Options, value); } }
- 由于參考分配是原子的,在分配參考時是否需要同步(使用鎖定或互鎖)?如果省略set邏輯,只維護get,還能保持原子性和同步性嗎?我充滿希望的想法是 get 中的鎖/互鎖用法將提供我正在尋找的同步。我曾嘗試撰寫示例程式來強制陳舊的值場景,但我無法可靠地完成它。
private ContactOptions _Options;
public ContactOptions Options {
get { return Interlocked.CompareExchange(ref _Options, null, null); }
set { _Options = value; } }
旁注:
- The ContactOptions class is deliberately immutable as I don't want to have to synchronize or worry about atomicity within the options themselves. They may contain any kind of data type, so I think it's a lot cleaner/safer to assign a new set of Options when a change is necessary.
- I'm familiar of the non-atomic implications of getting a value, working with that value, then setting the value. Consider the following snippet:
public class SomeInteger
{
private readonly object ValueLock = new object();
private int _Value;
public int Value { get { lock(ValueLock) { return _Value; } }
private set { lock(ValueLock) { _Value = value; } } };
// WRONG
public void manipulateBad()
{
Value ;
}
// OK
public void manipulateOk()
{
lock (ValueLock)
{
Value ;
// Or, even better: _Value ; // And remove the lock around the setter
}
}
}
Point being, I'm really only focused on the memory synchronization issue.
uj5u.com熱心網友回復:
如果我在這里錯了,請糾正我,但上面的代碼確實確保我在以執行緒安全的方式獲取 Options 的最新值時實作了獲取它的目標?使用此方法還有其他問題嗎?
是的,鎖會發出記憶體屏障,因此它將確保從記憶體中讀取值。除了可能比必須的更保守之外,沒有其他真正的問題。但是我有句話,如果有疑問,請使用鎖。
我相信鎖有一些開銷(轉換為 Monitor.Enter/Exit)。我認為我可以使用 Interlocked 來獲得名義上的性能提升,但對我來說更重要的是一組更清晰的代碼。以下作業可以實作同步嗎?
Interlocked 也應該發出記憶體屏障,所以我認為這應該或多或少地做同樣的事情。
由于參考分配是原子的,在分配參考時是否需要同步(使用鎖定或互鎖)?如果省略set邏輯,只維護get,還能保持原子性和同步性嗎?我充滿希望的想法是 get 中的鎖/互鎖用法將提供我正在尋找的同步。我曾嘗試撰寫示例程式來強制陳舊的值場景,但我無法可靠地完成它。
我認為在這種情況下,僅使欄位變得不穩定就足夠了。據我了解,“陳舊值”的問題有些夸張,快取一致性協議應該解決大多數問題。
據我所知,主要問題是阻止編譯器只是將值放入暫存器中,而根本不進行任何后續加載。這volatile應該可以防止這種情況,迫使編譯器在每次讀取時發出負載。但這主要是在回圈中重復檢查值時出現的問題。
但是只看一個屬性并不是很有用。當您有多個需要同步的值時,問題會更頻繁地出現。一個潛在的問題是編譯器或處理器對指令重新排序。鎖和記憶體屏障阻止了這種重新排序,但如果這是一個潛在的問題,最好鎖定更大的代碼段。
總的來說,我認為在處理多個執行緒時保持偏執是明智的。使用多同步可能比少同步好。一種例外情況是可能由過多鎖引起的死鎖。我對此的建議是在持有鎖時要非常小心你在呼叫什么。理想情況下,鎖應該只持有很短的、可預測的時間。
還要繼續使用純函式和不可變資料結構。這些是避免擔心執行緒問題的好方法。
uj5u.com熱心網友回復:
- 是的,
lock (OptionsLock)確保所有的執行緒將看到的最新值Options,因為記憶體屏障進入和退出時插入lock。 - 替換或類的
lockwith 方法同樣可以很好地實作最新值可見性目標。這些方法也插入了記憶體屏障。我認為使用更好地傳達代碼的意圖:InterlockedVolatileVolatile
public ContactOptions Options
{
get { return Volatile.Read(ref _Options); }
set { Volatile.Write(ref _Options, value); }
}
- 忽略訪問器
get或set訪問器中的同步會自動將您置于記憶體模型、快取一致性協議和 CPU 架構的大黑森林中。為了知道省略它是否安全,需要對目標硬體/作業系統配置有復雜的了解。您要么需要專家的建議,要么自己成為專家。如果您更喜歡留在軟體開發領域,請不要忽略同步!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/359315.html
標籤:c# multithreading locking thread-synchronization interlocked
上一篇:監控提交到執行緒池的任務是否超時
