C# 仅释放到最后一个线程的线程同步(锁定)
当中间线程不获取锁时,如何确保只有“最后一个”线程可以访问互斥锁/锁定区域 示例序列:C# 仅释放到最后一个线程的线程同步(锁定),c#,locking,mutex,cancellationtokensource,C#,Locking,Mutex,Cancellationtokensource,当中间线程不获取锁时,如何确保只有“最后一个”线程可以访问互斥锁/锁定区域 示例序列: A acquires lock B waits C waits B fails to acquire lock* A releases lock C acquires lock *B应该无法通过异常(如在信号量lim.Wait(CancellationToken)或布尔监视器.TryEnter()类型构造中)获取锁 我可以想到几个类似的方案来实现这一点(例如使用CancellationTokenSource
A acquires lock
B waits
C waits
B fails to acquire lock*
A releases lock
C acquires lock
*B应该无法通过异常(如在信号量lim.Wait(CancellationToken)
或布尔监视器.TryEnter()
类型构造中)获取锁
我可以想到几个类似的方案来实现这一点(例如使用CancellationTokenSource
和SemaphoreSlim
),但它们都不是特别优雅的
这种情况下有常见的做法吗?试试以下方法:
public interface ILocker
{
bool GetLock();
void Release();
}
class Locker : ILocker
{
private long m_NumberOfTimeGetLockWasCalled = 0;
private readonly object m_LockingObject = new object();
private readonly object m_LockingObject2 = new object();
public bool GetLock()
{
long lock_count = 0;
var lock_was_taken = false;
lock(m_LockingObject)
{
lock_count = m_NumberOfTimeGetLockWasCalled++;
lock_was_taken = Monitor.TryEnter(m_LockingObject2);
if (lock_was_taken)
return true;
}
while(!lock_was_taken)
{
Thread.Sleep(5);
lock(m_LockingObject)
{
if (lock_count != m_NumberOfTimeGetLockWasCalled)
return false;
lock_was_taken = Monitor.TryEnter(m_LockingObject2);
if (lock_was_taken)
break;
}
}
return true;
}
public void Release()
{
Monitor.Exit(m_LockingObject2);
}
}
这应该可以像您希望的那样工作,它使用大小为1的信号量LIM来控制它。我还添加了对传入CancelationToken以取消提前等待锁的支持,它还支持
WaitAsync
返回任务而不是阻塞
public sealed class LastInLocker : IDisposable
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
private CancellationTokenSource _cts = new CancellationTokenSource();
private bool _disposed = false;
public void Wait()
{
Wait(CancellationToken.None);
}
public void Wait(CancellationToken earlyCancellationToken)
{
if(_disposed)
throw new ObjectDisposedException("LastInLocker");
var token = ReplaceTokenSource(earlyCancellationToken);
_semaphore.Wait(token);
}
public Task WaitAsync()
{
return WaitAsync(CancellationToken.None);
}
public async Task WaitAsync(CancellationToken earlyCancellationToken)
{
if (_disposed)
throw new ObjectDisposedException("LastInLocker");
var token = ReplaceTokenSource(earlyCancellationToken);
//I await here because if ReplaceTokenSource thows a exception I want the
//observing of that exception to be deferred until the caller awaits my
//returned task.
await _semaphore.WaitAsync(token).ConfigureAwait(false);
}
public void Release()
{
if (_disposed)
throw new ObjectDisposedException("LastInLocker");
_semaphore.Release();
}
private CancellationToken ReplaceTokenSource(CancellationToken earlyCancellationToken)
{
var newSource = CancellationTokenSource.CreateLinkedTokenSource(earlyCancellationToken);
var oldSource = Interlocked.Exchange(ref _cts, newSource);
oldSource.Cancel();
oldSource.Dispose();
return newSource.Token;
}
public void Dispose()
{
_disposed = true;
_semaphore.Dispose();
_cts.Dispose();
}
}
下面是一个重新创建测试示例的小测试程序
internal class Program
{
static LastInLocker locker = new LastInLocker();
private static void Main(string[] args)
{
Task.Run(() => Test("A"));
Thread.Sleep(500);
Task.Run(() => Test("B"));
Thread.Sleep(500);
Task.Run(() => Test("C"));
Console.ReadLine();
}
private static void Test(string name)
{
Console.WriteLine("{0} waits for lock", name);
try
{
locker.Wait();
Console.WriteLine("{0} acquires lock", name);
Thread.Sleep(4000);
locker.Release();
Console.WriteLine("{0} releases lock", name);
}
catch (Exception)
{
Console.WriteLine("{0} fails to acquire lock", name);
}
}
}
输出
A waits for lock
A acquires lock
B waits for lock
C waits for lock
B fails to acquire lock
A releases lock
C acquires lock
C releases lock
等待锁
获得锁
B等待锁
C等待锁
B未能获得锁
开锁
C获得锁
C释放锁
这是一个有趣的问题-如果-在
A
释放锁和C
获取锁之间-D
出现,会发生什么?我只是好奇-试图想出一个需要这种方法的场景。对不起,我不理解“B失败”你这么说是什么意思?你从线程窗口冻结了线程?@xxbbcc期望的结果是最后一个in(在你的场景中是D)成功地获得了锁,而中间线程没有(B&C没有进入锁定部分)。@Kapoor我的意思是B没有获得锁,或者是通过异常(如SemaphoreSlim.Wait)(CancellationToken
)或布尔值监视器。TryEnter
类型构造。@AndrewHanlon是的,这正是我所怀疑的。但很难定义什么是“最后一个”。我猜一旦向线程授予锁的过程开始,任何新线程都是“第一个”.Hi Scott,谢谢你的详细回答。这与我一直使用的方法非常接近(只是用联锁的.Exchange替换内部锁)。但是将其包装到可重用类中是一种非常好的方法。@AndrewHanlon我想要.Cancel()
新的令牌分配将是一个原子操作,否则我可能会做类似的操作。我只需要重新编写一点,我想我可以用联锁交换来完成,但这个锁可能会非常小,而且通常是无争用的,所以开销不会太大。我一直希望/认为应该有一个更简单的方法完成此场景非常简单,但事实上这可能是正确的方法。@AndrewHanlon这很容易,只要我想一想,我就用Interlocked.Exchange更新了答案