5

确保只有“后进”线程才能访问互斥锁/锁定区域而中间线程不获取锁的正确方法是什么?

示例序列:

A acquires lock
B waits
C waits
B fails to acquire lock*
A releases lock
C acquires lock

*B 应该无法通过异常(如 inSemaphoreSlim.Wait(CancellationToken)或布尔Monitor.TryEnter()类型构造)获取锁。

我可以想到几种类似的方案来实现这一点(例如使用CancellationTokenSourceand SemaphoreSlim),但它们似乎都不是特别优雅。

这种情况有一个常见的做法吗?

4

2 回答 2

2

这应该像你想要的那样工作,它使用大小为 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 等待锁
A 获得锁
B 等待锁定
C 等待锁定
B 获取锁失败
A 释放锁
C 获得锁
C 释放锁
于 2015-09-02T21:45:22.440 回答
0

试试这个:

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);
    }
}
于 2015-09-02T21:04:10.147 回答