.NET中的跨进程读写同步原语?

时间:2010-08-17 15:00:07

标签: .net synchronization locking readerwriterlock cross-process

是否存在跨进程工作的读/写锁定机制(类似于Mutex,但是读/写而不是独占锁定)?我想允许并发读访问,但是允许独占写访问。

6 个答案:

答案 0 :(得分:5)

Windows不包含跨进程Reader-Writer锁。可以使用Semaphore和Mutex的组合来构造一个(Mutex由作者独立访问或由读者持有,然后读者使用Semaphore释放其他读者 - 即编写者将等待互斥和读者)

但是,如果预计争用率很低(即没有线程长时间持有锁),那么互斥可能仍然会更快:读写器锁的额外复杂性压倒了允许多个读者进入的任何好处。(A如果有更多的读者和锁定持续很长时间,读写器锁只会更快 - 但只有你的分析可以确认这一点。)

答案 1 :(得分:5)

没有。正如理查德在上面提到的那样,.NET中没有这种开箱即用的机制。 这是使用互斥锁和信号量实现它的方法。

方法#1在http://www.joecheng.com/blog/entries/Writinganinter-processRea.html中描述,引用:

// create or open global mutex
GlobalMutex mutex = new GlobalMutex("IdOfProtectedResource.Mutex");
// create or open global semaphore
int MoreThanMaxNumberOfReadersEver = 100;

GlobalSemaphore semaphore = new GlobalSemaphore("IdOfProtectedResource.Semaphore", MoreThanMaxNumberOfReadersEver);

public void AcquireReadLock()
{
  mutex.Acquire();
  semaphore.Acquire();
  mutex.Release();
}

public void ReleaseReadLock()
{
  semaphore.Release();
}

public void AcquireWriteLock()
{
  mutex.Acquire();
  for (int i = 0; i < MoreThanMaxNumberOfReadersEver; i++)
    semaphore.Acquire(); // drain out all readers-in-progress
  mutex.Release();
}

public void ReleaseWriteLock()
{
  for (int i = 0; i < MoreThanMaxNumberOfReadersEver; i++)
    semaphore.Release();
}

另一种选择是:

读取锁定 - 如上所述。写锁定如下(伪代码):

- Lock mutex
- Busy loop until the samaphore is not taken AT ALL:
-- wait, release.
-- Release returns value; 
-- if value N-1 then break loop.
-- yield (give up CPU cycle) by using Sleep(1) or alternative
- Do write
- Release mutex

必须注意的是,可以采用更有效的方法,如下所示:http://en.wikipedia.org/wiki/Readers-writers_problem#The_second_readers-writers_problem 在上面的文章中查找“此解决方案不是最理想”的字样。

答案 2 :(得分:0)

我已经根据Pavel的回答创建了此类。我尚未对其进行广泛的测试,但是我已经创建了一个简单的winforms应用程序来对其进行测试,到目前为止,它仍然可以正常工作。

请注意,它使用信号量,因此不支持重入。

public class CrossProcessReaderWriterLock
{
    private readonly string _name;
    const int _maxReaders = 10;

    readonly Mutex     _mutex;
    readonly Semaphore _semaphore;

    public CrossProcessReaderWriterLock(string name)
    {
        _name = name;
        _mutex     = new Mutex(false, name + ".Mutex");
        _semaphore = new Semaphore(_maxReaders, _maxReaders, name + ".Semaphore");
    }

    public void AcquireReaderLock()
    {
        //Log.Info($"{_name} acquiring reader lock...");

        _mutex    .WaitOne();
        _semaphore.WaitOne();
        _mutex    .ReleaseMutex();

        //Log.Info($"{_name} reader lock acquired.");
    }

    public void ReleaseReaderLock()
    {
        _semaphore.Release();

        //Log.Info($"{_name} reader lock released.");
    }

    public void AcquireWriterLock()
    {
        //Log.Info($"{_name} acquiring writer lock...");

        _mutex.WaitOne();

        for (int i = 0; i < _maxReaders; i++)
            _semaphore.WaitOne(); // drain out all readers-in-progress

        _mutex.ReleaseMutex();

        //Log.Info($"{_name} writer lock acquired.");
    }

    public void ReleaseWriterLock()
    {
        for (int i = 0; i < _maxReaders; i++)
            _semaphore.Release();

        //Log.Info($"{_name} writer lock released.");
    }
}

答案 3 :(得分:0)

如果您想避免作家饥饿,那么您可以考虑另一种算法。我研究了一些算法,可以避免 Writer 问题的饥饿(例如在这个 paper 中)。解决方案提议伪代码之一如下:pseudo-code image.

public class ReadWriterSynchronizer : IDisposable
{
    public ReadWriterSynchronizer(string name, int maxReaderCount)
    {
        myIncomingOperation = new Semaphore(1, 1, name + ".Incoming");
        myReadOperation = new Semaphore(1, 1, name + ".Reader");
        myWriteOperation = new Semaphore(1, 1, name + ".Writer");
        myCrossprocessCounter = new ReaderCounter(name + ".Counter", maxReaderCount);
    }

    public void EnterReadLock()
    {
        myIncomingOperation.WaitOne();
        myReadOperation.WaitOne();

        // Local variable is necessary, because of optimalization
        int currentCount = myCrossprocessCounter.Increase();
        if (currentCount == 1)
        {
            myWriteOperation.WaitOne();
        }

        myReadOperation.Release();
        myIncomingOperation.Release();
    }

    public void ExitReadLock()
    {
        myReadOperation.WaitOne();

        // Local variable is necessary, because of optimalization
        int currentCount = myCrossprocessCounter.Decrease();
        if (currentCount == 0)
        {
            myWriteOperation.Release();
        }

        myReadOperation.Release();
    }

    public void EnterWriteLock()
    {
        myIncomingOperation.WaitOne();
        myWriteOperation.WaitOne();
    }

    public void ExitWriteLock()
    {
        myWriteOperation.Release();
        myIncomingOperation.Release();
    }

    public void Dispose()
    {
        myIncomingOperation?.Dispose();
        myReadOperation?.Dispose();
        myWriteOperation?.Dispose();
        myCrossprocessCounter?.Dispose();

        GC.SuppressFinalize(this);
    }

    private readonly ReaderCounter myCrossprocessCounter;
    private readonly Semaphore myIncomingOperation;
    private readonly Semaphore myReadOperation;
    private readonly Semaphore myWriteOperation;
}

不幸的是,ctr 变量是一个整数,因此它只能在进程间场景中工作。我决定用信号量计数器 (ReaderCounter) 替换整数计数器,以便它可以用于跨进程通信。本质上,我使用 WaitOne(0)减少,使用 Release()增加读者计数器。

internal class ReaderCounter : IDisposable
{
    internal ReaderCounter(string name, int maxConcurrentRead)
    {
        MaximumCount = maxConcurrentRead + InitialCount;
        myReadCounterSemaphore = new Semaphore(InitialCount, MaximumCount, name);
        myIncomingOperation = new Semaphore(1, 1, name + ".Incoming");
    }

    internal int Increase()
    {
        int counter = RetrieveCurrentCount();

        // Not allowing to exceed maximum count
        if (counter != MaximumCount - 1)
        {
            counter = myReadCounterSemaphore.Release();
        }
        else
        {
            counter++;
        }

        return counter;
    }

    internal int Decrease()
    {
        int counter = RetrieveCurrentCount() - 1;
        myReadCounterSemaphore.WaitOne(0);

        return counter;
    }

    public void Dispose()
    {
        myReadCounterSemaphore?.Dispose();
        myIncomingOperation?.Dispose();

        GC.SuppressFinalize(this);
    }

    internal int MaximumCount { get; private set; }

    private const int InitialCount = 1;
    private readonly Semaphore myReadCounterSemaphore;
    private readonly Semaphore myIncomingOperation;

    private int RetrieveCurrentCount()
    {
        myReadCounterSemaphore.WaitOne(0);
        int counter = myReadCounterSemaphore.Release();
        return counter;
    }
}

注意:为了更容易使用,阅读器计数器中添加了 1 个吹嘴计数。例如,使用 5 个读取器意味着 [1,6] 初始信号量计数。从最小计数返回 -1,从最大计数返回 +1。

更新:我已经创建了一个带有控制台应用程序的 GitHub 存储库,因此您可以使用它。它还包含带有 TryEnterReadLock()TryEnterWriteLock() 方法的 ReaderWriterSynchronizer:https://github.com/SzilvasiPeter/Cross-process-ReaderWriterLock

答案 4 :(得分:-1)

System.Threading.Mutex有一个可用于进程内通信的互斥锁。如果您想要它不支持的功能,可以通过互斥锁实现。

答案 5 :(得分:-2)

你看过System.Threading.ReaderWriteLock了吗?这是MSDN链接。