使用命名互斥锁来锁定文件

问题描述 投票:0回答:4

我正在使用命名互斥体来锁定对文件(路径为“strFilePath”)的访问,如下所示:

private void DoSomethingsWithAFile(string strFilePath)
{
      Mutex mutex = new Mutex(false,strFilePath.Replace("\\",""));
      try
      {
         mutex.WaitOne();
         //do something with the file....
      }
      catch(Exception ex)
      {
         //handle exception
      }
      finally
      {
         mutex.ReleaseMutex();
      }
 }

因此,这样代码只会在已经处理同一个文件时阻塞线程。 嗯,我测试了这个,似乎工作正常,但我真的很想知道你对此的想法。

c# .net producer-consumer
4个回答
4
投票

我遇到了同样的问题,许多线程可以在同一个文件中写入。

互斥量不好的原因之一是它速度慢:

duration of call mutexSyncTest: 00:00:08.9795826    
duration of call NamedLockTest: 00:00:00.2565797

BlockingCollection 集合 - 非常好的主意,但对于我的很少发生冲突的情况,并行写入比串行写入更好。另外,使用字典的方式更容易实现。

我使用这个解决方案(更新):

public class NamedLock
{
    private class LockAndRefCounter
    {
        public long refCount;
    }

    private ConcurrentDictionary<string, LockAndRefCounter> locksDictionary = new ConcurrentDictionary<string, LockAndRefCounter>();

    public void DoWithLockBy(string key, Action actionWithLock)
    {
        var lockObject = new LockAndRefCounter();

        var keyLock = locksDictionary.GetOrAdd(key, lockObject);
        Interlocked.Increment(ref keyLock.refCount);

        lock (keyLock)
        {
            actionWithLock();

            Interlocked.Decrement(ref keyLock.refCount);
            if (Interlocked.Read(ref keyLock.refCount) <= 0)
            {
                LockAndRefCounter removed;
                locksDictionary.TryRemove(key, out removed);
            }
        }
    }
}

3
投票

既然您正在谈论具有多个线程的生产者-消费者情况,“标准解决方案是使用

BlockingCollection
,它是 .NET 4 及更高版本的一部分 - 几个包含信息的链接:

如果您只想让锁定过程正常工作,那么:

ConcurrentDictionary
TryAdd
方法调用结合使用...如果它返回
true
那么文件没有“锁定”,现在“锁定”,因此线程可以继续 - 并“解锁”通过在最后调用
Remove
来实现...任何其他线程同时获得
false
并可以决定要做什么...

我绝对会推荐

BlockingCollection
方法!


1
投票

另一种选择是:创建一个在队列上工作的消费者线程,如果队列为空则阻塞。您可以让多个生产者线程向此队列添加多个文件路径并通知消费者。

自 .net 4.0 以来,出现了一个不错的新类:

System.Collections.Concurrent.BlockingCollection<T>

不久前,我在 Stack Overflow 上遇到了同样的 问题 - 如何实现我自己的高级生产者/消费者场景?


0
投票

对于像我这样将来偶然发现这个线程的人 - @gabba 给出的答案非常有效(通过从 512 个并发线程访问同一文件并执行写入、读取和文件删除进行测试),但是如果计算引用不重要,代码如下:

public class NamedLock
{
    private class LockAndRefCounter
    {
        public long refCount;
    }

    private ConcurrentDictionary<string, LockAndRefCounter> locksDictionary = new ConcurrentDictionary<string, LockAndRefCounter>();

    public void DoWithLockBy(string key, Action actionWithLock)
    {
        var lockObject = new LockAndRefCounter();

        var keyLock = locksDictionary.GetOrAdd(key, lockObject);
        Interlocked.Increment(ref keyLock.refCount);

        lock (keyLock)
        {
            actionWithLock();

            Interlocked.Decrement(ref keyLock.refCount);
            if (Interlocked.Read(ref keyLock.refCount) <= 0)
            {
                LockAndRefCounter removed;
                locksDictionary.TryRemove(key, out removed);
            }
        }
    }
}

可以简化为:

public class NamedLock
{
    private class LockObject { /* Empty */ }
    private static readonly ConcurrentDictionary<string, LockObject> locks = new();

    public void DoWithLockBy(string key, Action actionWithLock)
    {
        LockObject _lock = locks.GetOrAdd(key, new LockObject());
        lock (_lock)
        {
            action();
        }
    }
}

结果是每个文件路径的空对象 - 即“命名”锁。

lock
语句不会将锁定对象从字典中删除,而是用作访问此文件路径的所有线程的互斥体。

© www.soinside.com 2019 - 2024. All rights reserved.