【发布时间】:2015-05-26 17:31:58
【问题描述】:
我有一个线程安全类,它使用需要以独占方式访问的特定资源。在我的评估中,让各种方法的调用者阻塞 Monitor.Enter 或等待 SemaphoreSlim 以访问此资源是没有意义的。
例如,我有一些“昂贵”的异步初始化。由于多次初始化没有意义,无论是来自多个线程还是单个线程,多个调用都应该立即返回(甚至抛出异常)。相反,应该创建、初始化并然后将实例分发到多个线程。
更新 1:
MyClass 在任一方向使用两个NamedPipes。 InitBeforeDistribute 方法并不是真正的初始化,而是正确地建立了双向连接。在设置连接之前使管道可用于N 线程是没有意义的。一旦设置好多个线程可以发布工作,但实际上只有一个线程可以读取/写入流。我很抱歉用糟糕的示例命名混淆了这一点。
更新 2:
如果InitBeforeDistribute 使用适当的等待逻辑实现了SemaphoreSlim(1, 1)(而不是互锁操作引发异常),那么 Add/Do Square 方法是否可行?它不会抛出冗余异常(如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);
}
}
}
几点:
- 如果存在阻塞/等待对锁的访问导致 完美的感觉,那么这个例子就没有意义(也就是说)。
- 由于我需要在方法中等待,我必须使用类似 SemaphoreSlim 如果我在哪里使用“正确”的锁。放弃 上面例子的信号量让我不用担心 一旦我完成了课程,就处理它。 (我一直不喜欢 处理多个线程使用的项目的想法。这是一个次要 肯定的。)
- 如果经常调用该方法,可能会有一些性能 好处,当然应该衡量。
上面的例子在参考文献中没有意义。到 (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 上的“无锁”代码有关的问题通常都反对它,并指出它是针对“专家”的。很少(我可能错了)我会看到可以深入研究的书籍/博客/等建议,如果有人愿意的话。如果我应该研究任何此类资源,请分享。任何建议将不胜感激!
【问题讨论】:
-
您有什么具体问题吗?
-
所以,为了确保我理解你......你有一个昂贵的初始化过程,当第一个线程访问资源时调用它。尝试访问资源的后续线程的用例是什么?从您的帖子中,听起来您希望它们抛出异常或立即返回(不获取数据?),这两种方法听起来都不比在初始化完成之前简单地阻塞更合适。我错过了什么吗?当某事需要独占访问时,这听起来像是锁的确切用例。
-
AsyncLock是你的朋友。不要想太多。 -
我可能在这里遗漏了一些东西,但是您为什么不简单地使用
Lazy<T>进行线程安全初始化? -
顺便说一句,我想不出一个用例可以同时使用“无锁”和“异步”。
标签: c# multithreading asynchronous lock-free interlocked