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

发布于 2024-09-14 17:07:27 字数 65 浏览 6 评论 0原文

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

Is there a read/write locking mechanism that works across processes (similar to Mutex, but read/write instead exclusive locking)? I would like to allow concurrent read access, but exclusive write access.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

兲鉂ぱ嘚淚 2024-09-21 17:07:27

Windows 不包含跨进程读写锁。可以使用信号量和互斥量的组合来构造互斥量(互斥量由写入者持有以进行独占访问,或者由读取者持有,然后读取者使用信号量释放其他读取者,即写入者将仅等待互斥体,而读取者则等待) 。

但是,如果预期争用较低(即没有线程长时间持有锁),则互斥可能仍然会更快:读写器锁的额外复杂性压倒了允许多个读取器进入的任何好处。(读写器锁)只有当有更多的读取器并且锁被持有很长一段时间时,锁才会更快,但只有您的分析才能证实这一点。)

Windows does not include a cross process Reader-Writer lock. A combination of Semaphore and Mutex could be used to construct ones (the Mutex is held by a writer for exclusive access or by a Reader which then uses the Semaphore to release other readers—i.e. writers would wait on just the mutex and readers for either).

However, if contention is expected to be low (i.e. no thread holds a lock for long) then mutual exclusion may still be faster: the additional complexity of the reader-writer lock overwhelms any benefit of allowing multiple readers in. (A reader-writer lock will only be faster if there are many more readers and locks are held for significant time—but only your profiling can confirm this.)

囍笑 2024-09-21 17:07:27

不会。正如 Richard 上面指出的,.NET 中不存在这种开箱即用的机制。
这是使用互斥锁和信号量来实现它的方法。

方法 #1 在 http://www.joe Cheng.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
在上面的文章中查找“此解决方案不是最优的”字样。

No. As Richard noted above, there is no such out of the box mechanism in .NET.
This is how to implement it using a mutex and a semaphore.

Method #1 is described in http://www.joecheng.com/blog/entries/Writinganinter-processRea.html, quoting:

// 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();
}

An alternative would be:

Read locking - as above. Write locking as follows (pseudocode):

- 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

It must be noted that more efficient approach is possible, as here: http://en.wikipedia.org/wiki/Readers-writers_problem#The_second_readers-writers_problem
Look for the words "This solution is suboptimal" in the article above.

后来的我们 2024-09-21 17:07:27

我根据帕维尔的回答创建了这个课程。我还没有对其进行广泛的测试,但我已经创建了一个简单的 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.");
    }
}

I've created this class based on Pavel's answer. I haven't tested it extensively yet, but I've created a simple winforms application to test it and so far it works well.

Please note, that it uses a semaphore, so it doesn't support reentrancy.

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.");
    }
}
勿忘初心 2024-09-21 17:07:27

如果您想避免作家饥饿,那么您可以考虑另一种算法。我研究了一些算法,这些算法避免了 Writer 问题的饥饿(例如在这篇论文中)。解决方案建议伪代码之一如下:伪代码图像

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

If you want to avoid Writer starvation then you could consider another algorithm. I research some algorithms, which avoid the starvation of the Writer problem (e.g. in this paper). One of the solution proposal pseudo-code is the following: 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;
}

Unfortunately, ctr variable is an integer, therefore it could only work in interprocess scenarios. I decided to replace the integer counter with a Semaphore counter (ReaderCounter) so it could be used for cross-process communication. Essentially, I used WaitOne(0) in order to decrease and Release() to increase the reader counter.

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;
    }
}

NOTE: For easier usage, 1 puffer count was added to the reader counter. For example, using a 5 reader means [1,6] initial Semaphore count. Decreasing from the minimum count returns with -1 and Increasing from maximum count return with maximum count +1.

UPDATE: I have created a GitHub repository with console applications, so you can play with it. It also contains ReaderWriterSynchronizer with TryEnterReadLock() and TryEnterWriteLock() methods: https://github.com/SzilvasiPeter/Cross-process-ReaderWriterLock

夏雨凉 2024-09-21 17:07:27

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

System.Threading.Mutex has a mutex that can be used for intra-process communication. If you would like functionality that it doesn't support, it can be implemented via a mutex.

你又不是我 2024-09-21 17:07:27

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

Have you looked at System.Threading.ReaderWriteLock? Here's the MSDN Link.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文