And*_* A. 41 c# multithreading interlocked thread-safety
只是检查...... _count正在安全访问,对吗?
两个方法都由多个线程访问.
private int _count;
public void CheckForWork() {
if (_count >= MAXIMUM) return;
Interlocked.Increment(ref _count);
Task t = Task.Run(() => Work());
t.ContinueWith(CompletedWorkHandler);
}
public void CompletedWorkHandler(Task completedTask) {
Interlocked.Decrement(ref _count);
// Handle errors, etc...
}
Run Code Online (Sandbox Code Playgroud)
Eri*_*ert 98
这是线程安全的,对吗?
假设MAXIMUM为1,count为零,五个线程调用CheckForWork.
所有五个线程都可以验证计数小于MAXIMUM.然后,计数器将被提升至五个,并且将开始五个工作.
这似乎违背了代码的意图.
而且:该领域并不易变.那么什么机制可以保证任何线程都能读取无内存屏障路径上的最新值?没有什么可以保证的!如果条件为false,则仅创建内存屏障.
更一般地说:你在这里制造虚假的经济.通过使用低锁定解决方案,您可以节省无争议锁定所需的十几纳秒. 拿锁.你可以承受额外的十几纳秒.
更普遍的是:除非您是处理器体系结构专家并且知道允许CPU在低锁路径上执行的所有优化,否则不要编写低锁代码.你不是这样的专家.我也不是.这就是我不写低锁代码的原因.
Dav*_* S. 41
不,if (_count >= MAXIMUM) return;不是线程安全的.
编辑:你也必须锁定读取,然后逻辑上应该与增量分组,所以我重写像
private int _count;
private readonly Object _locker_ = new Object();
public void CheckForWork() {
lock(_locker_)
{
if (_count >= MAXIMUM)
return;
_count++;
}
Task.Run(() => Work());
}
public void CompletedWorkHandler() {
lock(_locker_)
{
_count--;
}
...
}
Run Code Online (Sandbox Code Playgroud)
Jim*_*hel 36
这就是Semaphore和SemaphoreSlim的用途:
private readonly SemaphoreSlim WorkSem = new SemaphoreSlim(Maximum);
public void CheckForWork() {
if (!WorkSem.Wait(0)) return;
Task.Run(() => Work());
}
public void CompletedWorkHandler() {
WorkSem.Release();
...
}
Run Code Online (Sandbox Code Playgroud)
Bri*_*eon 22
不,你有什么不安全.检查是否_count >= MAXIMUM可以与Interlocked.Increment来自另一个线程的调用竞争.其实,这是真的很难使用低锁定技术来解决.为了使其正常工作,您需要使一系列的几个操作显示为原子而不使用锁.那是困难的部分.这里涉及的一系列操作是:
_count_count >= MAXIMUM_count取决于做出的决定.如果你没有让所有这四个步骤看起来都是原子的那么就会出现竞争条件.用于在不进行锁定的情况下执行复杂操作的标准模式如下.
public static T InterlockedOperation<T>(ref T location)
{
T initial, computed;
do
{
initial = location;
computed = op(initial); // where op() represents the operation
}
while (Interlocked.CompareExchange(ref location, computed, initial) != initial);
return computed;
}
Run Code Online (Sandbox Code Playgroud)
注意发生了什么.重复执行该操作,直到ICX操作确定初始值在首次读取的时间和尝试改变它的时间之间没有改变.这是标准模式,并且由于CompareExchange(ICX)调用而发生魔术.但请注意,这并未考虑ABA问题.1
你能做什么:
因此,采用上述模式并将其合并到您的代码中将导致此问题.
public void CheckForWork()
{
int initial, computed;
do
{
initial = _count;
computed = initial < MAXIMUM ? initial + 1 : initial;
}
while (Interlocked.CompareExchange(ref _count, computed, initial) != initial);
if (replacement > initial)
{
Task.Run(() => Work());
}
}
Run Code Online (Sandbox Code Playgroud)
就个人而言,我会完全采用低锁策略.我上面提到的有几个问题.
你应该做什么:
使用硬锁定路径,您的代码可能如下所示.
private object _lock = new object();
private int _count;
public void CheckForWork()
{
lock (_lock)
{
if (_count >= MAXIMUM) return;
_count++;
}
Task.Run(() => Work());
}
public void CompletedWorkHandler()
{
lock (_lock)
{
_count--;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,这更简单,并且更不容易出错.实际上你可能会发现这种方法(硬锁)实际上比我上面显示的更快(低锁).同样,原因很棘手,有一些技术可以用来加快速度,但它超出了这个答案的范围.
1在这种情况下,ABA问题实际上不是问题,因为逻辑不依赖于_count保持不变.重要的是它的价值在两个时间点是相同的,无论两者之间发生了什么.换句话说,问题可以减少到一个似乎价值没有改变的问题,即使实际上它可能有.
| 归档时间: |
|
| 查看次数: |
3413 次 |
| 最近记录: |