我正在寻找允许多个线程读取共享资源(允许并发)的解决方案,但是一旦线程进入变异块就会锁定这些读取线程,以实现两全其美。
我查了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"));
答案 0 :(得分:2)
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);
}
}
无锁案件的好处:
缺点:
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));
}
}