读取并发但锁定变异

时间:2017-01-26 02:45:43

标签: c# multithreading concurrency locking

我正在寻找允许多个线程读取共享资源(允许并发)的解决方案,但是一旦线程进入变异块就会锁定这些读取线程,以实现两全其美。

我查了reference,但似乎解决方法是锁定读写线程。

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"));

2 个答案:

答案 0 :(得分:2)

此行为有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()一次,则需要稍微复杂的代码。

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>>();

答案 1 :(得分:0)

解决方案取决于您的要求。如果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);
    }
}

无锁案件的好处:

  • 我们没有读取锁定,因此我们获得最大性能。

缺点:

  • 我们必须复制data =&gt;内存流量(分配,垃圾收集)
  • 读者线程可以观察不到最近的更新(如果它在更新之前读取参考)
  • 如果读者多次使用sharedResource引用,那么我们必须通过Interlocked.Exchange将此引用复制到本地变量(如果此引用的用法假设它是相同的集合)
  • 如果sharedResource是可变对象的列表,那么我们必须小心更新mutating中的这些对象,因为读者可能在同一时刻使用它们=&gt;在这种情况下,最好还复制这些对象
  • 如果有多个更新程序线程,那么我们必须在Interlocked.CompareExchange中使用Interlocked.Exchange代替mutating并使用一种循环

所以,如果你想要无锁,那么使用不可变对象会更好。无论如何,你将为内存分配/ 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));
    }
}