[英]Lock-free, awaitable, exclusive access methods
我有一个线程安全类,该类使用需要专门访问的特定资源。 以我的评估,在Monitor.Enter
上阻止各种方法的调用者是没有意义的。输入或等待SemaphoreSlim
以访问此资源。
例如,我有一些“昂贵的”异步初始化。 由于多次初始化(从多个线程还是单个线程)没有意义,因此多个调用应立即返回(甚至引发异常)。 相反,应该创建,初始化然后将实例分发到多个线程。
更新1:
MyClass
在两个方向上都使用两个NamedPipes
。 InitBeforeDistribute
方法并不是真正的初始化,而是正确地在两个方向上建立连接。 在建立连接之前,使管道可用于N
线程是没有意义的。 设置完成后,多个线程可以发布工作,但是实际上只有一个线程可以读取/写入流。 我为这些示例的命名不合理而感到困惑。
更新2:
如果InitBeforeDistribute
使用适当的等待逻辑实现了SemaphoreSlim(1, 1)
(而不是互锁的操作抛出异常),那么Add / Do Square方法是否可行? 它没有锁时不会抛出冗余异常(例如InitBeforeDistribute
中的InitBeforeDistribute
)吗?
以下将是 好 不好的例子:
class MyClass
{
private int m_isIniting = 0; // exclusive access "lock"
private volatile bool vm_isInited = false; // vol. because other methods will read it
public async Task InitBeforeDistribute()
{
if (Interlocked.Exchange(ref this.m_isIniting, -1) != 0)
throw new InvalidOperationException(
"Cannot init concurrently! Did you distribute before init was finished?");
try
{
if (this.vm_isInited)
return;
await Task.Delay(5000) // init asynchronously
.ConfigureAwait(false);
this.vm_isInited = true;
}
finally
{
Interlocked.Exchange(ref this.m_isConnecting, 0);
}
}
}
一些要点:
上面的示例在参考文献中没有任何意义。 至(3.),因此这是另一个示例:
class MyClass
{
private volatile bool vm_isInited = false; // see above example
private int m_isWorking = 0; // exclusive access "lock"
private readonly ConcurrentQueue<Tuple<int, TaskCompletionSource<int>> m_squareWork =
new ConcurrentQueue<Tuple<int, TaskCompletionSource<int>>();
public Task<int> AddSquare(int number)
{
if (!this.vm_isInited) // see above example
throw new InvalidOperationException(
"You forgot to init! Did you already distribute?");
var work = new Tuple<int, TaskCompletionSource<int>(number, new TaskCompletionSource<int>()
this.m_squareWork.Enqueue(work);
Task do = DoSquare();
return work.Item2.Task;
}
private async Task DoSquare()
{
if (Interlocked.Exchange(ref this.m_isWorking, -1) != 0)
return; // let someone else do the work for you
do
{
try
{
Tuple<int, TaskCompletionSource<int> work;
while (this.m_squareWork.TryDequeue(out work))
{
await Task.Delay(5000) // Limiting resource that can only be
.ConfigureAwait(false); // used by one thread at a time.
work.Item2.TrySetResult(work.Item1 * work.Item1);
}
}
finally
{
Interlocked.Exchange(ref this.m_isWorking, 0);
}
} while (this.m_squareWork.Count != 0 &&
Interlocked.Exchange(ref this.m_isWorking, -1) == 0)
}
}
我应该注意这个“无锁”示例的某些特定负面影响吗?
关于SO上的“无锁”代码的大多数问题通常都建议不要这样做,并指出这是给“专家”的。 很少(我可能在这本书上写错了),如果有人倾向于的话,我是否能看到有关书籍/博客/等方面的建议? 如果有任何此类资源可供参考,请分享。 任何建议将不胜感激!
更新:相关的好文章
关于无lock-free
要点不是针对experts
。
要点是Do you really need lock-free algorythm here?
我在这里无法理解您的逻辑:
由于多次初始化(从多个线程还是单个线程)没有意义,因此多个调用应立即返回 (甚至引发异常)。
为什么您的用户不能简单地等待初始化结果,然后再使用您的资源? 如果可以的话,只需使用Lazy<T>
类,甚至使用Asynchronous Lazy Initialization
。
您确实应该阅读有关共识号和CAS运算的知识,以及在实现自己的同步原语时为何如此重要。
在您的代码中,您使用的是Interlocked.Exchange
方法,该方法不是真正的CAS
,因为它始终交换值,并且其共识数等于2
。 这意味着使用这种构造的原语只能在2
线程中正常工作(不是您的情况,而是2
)。
我试图定义是您的代码在3
线程中正常工作,还是可能在某些情况下导致您的应用程序进入损坏状态,但是30
分钟后我停了下来。 经过一段时间的努力,您的任何团队成员都会像我一样停下来理解您的代码。 这不仅浪费时间,而且浪费您的团队。 除非确实需要,否则不要重新发明轮子。
我在相关领域中最喜欢的书是Ben Watson 编写的高性能.NET代码 ,而我最喜欢的博客是Stephen Cleary的博客。 如果您可以更具体地了解自己感兴趣的书籍,则可以添加更多参考资料。
程序中没有锁不会使您的应用程序无lock-free
。 在.NET应用程序中,实际上不应在内部程序流中使用Exceptions
。 考虑一下操作系统并没有安排初始化线程一段时间(出于各种原因,无论它们到底是什么)。
在这种情况下,应用程序中的所有其他线程将逐步尝试访问共享资源而死亡。 我不能说这是无lock-free
代码。 是的,其中没有锁,但是不能保证程序的正确性,因此按定义它不是无锁的 。
Maurice Herlihy和Nir Shavit撰写的《多处理器编程的艺术》是无锁和无等待编程的重要资源。 无锁是除编程模式以外的一种进度保证,因此要说一种算法是无锁的,则必须验证或显示该进度保证的证明。 简单地说,无锁意味着阻塞或停止一个线程不会阻塞其他线程的进度,或者如果一个线程被无限频繁地阻塞,那么还有另一个线程会无限地频繁进步。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.