[英]Concurrency with reading but locking with mutating
我正在尋找一種解決方案,該解決方案允許多個線程讀取共享資源(允許並發),但是一旦線程進入變異塊,然后鎖定這些讀取線程,以實現兩者的最佳。
我查閱了此參考資料,但似乎解決方案是鎖定讀寫線程。
class Foo {
List<string> sharedResource;
public void reading() // multiple reading threads allowed, concurrency ok, lock this only if a thread enters the mutating block below.
{
}
public void mutating() // this should lock any threads entering this block as well as lock the reading threads above
{
lock(this)
{
}
}
}
C#中有這樣的解決方案嗎?
編輯
在GetMultiton
和構造函數中輸入的所有線程都應返回相同的實例。 希望它們是線程安全的。
class Foo: IFoo {
public static IFoo GetMultiton(string key, Func<IFoo> fooRef)
{
if (instances.TryGetValue(key, out IFoo obj))
{
return obj;
}
return fooRef();
}
public Foo(string key) {
instances.Add(key, this);
}
}
protected static readonly IDictionary<string, IFoo> instances = new ConcurrentDictionary<string, IFoo>();
采用
Foo.GetMultiton("key1", () => new Foo("key1"));
此行為有一個預構建的類ReaderWriterLockSlim
class Foo {
List<string> sharedResource;
ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
public void reading() // multiple reading threads allowed, concurrency ok, lock this only if a thread enters the mutating block below.
{
_lock.EnterReadLock();
try
{
//Do reading stuff here.
}
finally
{
_lock.ExitReadLock();
}
}
public void mutating() // this should lock any threads entering this block as well as lock the reading threads above
{
_lock.EnterWriteLock();
try
{
//Do writing stuff here.
}
finally
{
_lock.ExitWriteLock();
}
}
}
多個線程可以同時進入讀取鎖,但是如果嘗試使用寫入鎖,它將阻塞直到所有當前讀取器完成,然后阻塞所有新的寫入器和新讀取器,直到寫入鎖完成。
更新后,您根本不需要鎖。 只需使用ConcurrentDictionary中的GetOrAdd
class Foo: IFoo {
public static IFoo GetMultiton(string key, Func<IFoo> fooRef)
{
return instances.GetOrAdd(key, k=> fooRef());
}
public Foo(string key) {
instances.Add(key, this);
}
}
注意, fooRef()
可能會被多次調用,但是只有第一個返回的fooRef()
將被用作所有線程的結果。 如果只想一次調用fooRef()
,則將需要稍微復雜一些的代碼。
class Foo: IFoo {
public static IFoo GetMultiton(string key, Func<IFoo> fooRef)
{
return instances.GetOrAdd(key, k=> new Lazy<IFoo>(fooRef)).Value;
}
public Foo(string key) {
instances.Add(key, new Lazy<IFoo>(()=>this);
}
}
protected static readonly IDictionary<string, Lazy<IFoo>> instances = new ConcurrentDictionary<string, Lazy<IFoo>>();
解決方案取決於您的要求。 如果ReaderWriterLockSlim
性能(請注意,它比當前.NET Framework中的常規鎖慢大約兩倍,因此如果您很少修改並且讀取操作相當繁重,則可以實現最高性能,否則開銷將超過利潤),您可以嘗試創建數據副本,對其進行修改,並在Interlocked
類的幫助下自動交換引用(如果不需要在每個線程更改后立即擁有最新數據)。
class Foo
{
IReadOnlyList<string> sharedResource = new List<string>();
public void reading()
{
// Here you can safely* read from sharedResource
}
public void mutating()
{
var copyOfData = new List<string>(sharedResource);
// modify copyOfData here
// Following line is correct only in case of single writer:
Interlocked.Exchange(ref sharedResource, copyOfData);
}
}
免鎖外殼的優點:
缺點:
sharedResource
引用,則必須通過Interlocked.Exchange
將此引用復制到本地變量(如果引用的這種用法假定它是同一集合) sharedResource
是可變的對象的列表,那么我們必須要小心在更新此對象的mutating
,因為讀者可能會使用他們在同一時刻=>在這種情況下,最好讓這些對象的副本,以及 mutating
和某種循環中使用Interlocked.CompareExchange
而不是Interlocked.Exchange
因此,如果您希望不加鎖,那么最好使用不可變的對象。 而且無論如何,您都需要為性能分配內存/ GC。
更新
這是允許多個作者的版本:
class Foo
{
IReadOnlyList<string> sharedResource = new List<string>();
public void reading()
{
// Here you can safely* read from sharedResource
}
public void mutating()
{
IReadOnlyList<string> referenceToCollectionForCopying;
List<string> copyOfData;
do
{
referenceToCollectionForCopying = Volatile.Read(ref sharedResource);
copyOfData = new List<string>(referenceToCollectionForCopying);
// modify copyOfData here
} while (!ReferenceEquals(Interlocked.CompareExchange(ref sharedResource, copyOfData,
referenceToCollectionForCopying), referenceToCollectionForCopying));
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.