只释放给后进线程的线程同步(加锁)

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() 类型构造。

我能想到几种类似的方案来实现这一点(例如使用 CancellationTokenSourceSemaphoreSlim),但其中 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