簡體   English   中英

與讀取並發,但與變異並發

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

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