只释放给后进线程的线程同步(加锁)
Thread synchronization (locking) that only releases to the last-in thread
确保只有 'last-in' 线程被授予访问 mutex/locked 区域而中间线程不获取锁的正确方法是什么?
示例序列:
A acquires lock
B waits
C waits
B fails to acquire lock*
A releases lock
C acquires lock
*B 应该无法通过异常获取锁(如 SemaphoreSlim.Wait(CancellationToken)
或布尔 Monitor.TryEnter()
类型构造。
我能想到几种类似的方案来实现这一点(例如使用 CancellationTokenSource
和 SemaphoreSlim
),但其中 none 看起来特别优雅。
这种情况有通用的做法吗?
试试这个:
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 的 SemaphoreSlim 来控制它。我还添加了对传入 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
确保只有 'last-in' 线程被授予访问 mutex/locked 区域而中间线程不获取锁的正确方法是什么?
示例序列:
A acquires lock
B waits
C waits
B fails to acquire lock*
A releases lock
C acquires lock
*B 应该无法通过异常获取锁(如 SemaphoreSlim.Wait(CancellationToken)
或布尔 Monitor.TryEnter()
类型构造。
我能想到几种类似的方案来实现这一点(例如使用 CancellationTokenSource
和 SemaphoreSlim
),但其中 none 看起来特别优雅。
这种情况有通用的做法吗?
试试这个:
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 的 SemaphoreSlim 来控制它。我还添加了对传入 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