Using a named mutex to lock a file
Asked Answered
Q

4

9

I'm using a named mutex to lock access to a file (with path 'strFilePath') in a construction like this:

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

So, this way the code will only block the thread when the same file is being processed already. Well, I tested this and seemed to work okay, but I really would like to know your thoughts about this.

Quartana answered 31/1, 2012 at 10:1 Comment(18)
are you trying to implement a classic producer-consumer-scenario? give us more information on your scenario - it's very hard to work/help with that trimmed view on your actual problem!Frasch
any other software like Windows Explorer, Notepad etc. does NOT know anything about your "mutex-based lock" and will happily ignore it!Camise
@Camise Yes, I know, but that is not the idea. I use this construct for thread synchronization within my program. If I don not want other programs to acces the file, I'll just use the ordinary filelocking mechanisms.Quartana
@Andreas Well, I have different threads in my code that will write/remove data to/from a file, which I synchronize like this.Quartana
@Quartana you need to provide much more information then... you just need a mechanism to "lock" tiles inside your multi-threaded process ? what .NET version are you using ?Camise
named-mutexes include n consumers of these mutexes, which is definitely not the correct way to go. rather go for a solution which uses a proper ratio of consumers of your mutexes/locks and cores (if you are after multithreading!)Frasch
@yahia Yes, I need to lock files inside my code. using .net 3.5. in a way that in theory n threads may execute the DoSomethingsWithAFile function, but have to wait if the file is already being processedQuartana
once again: having n threads doing i/o and being blocked by mutexes will definitely not improve your performance!Frasch
@Quartana any chance you could go with .NET 4 ?Camise
@Andreas what do you mean by 'n consumers' for a named mutex?Quartana
@Andreas the n is theoretically. In fact there is a maximum of 6 threads... ;-)Quartana
by consumers i mean completely independent threads. it's not that good idea to have n threads/consumsers waiting for a lock/mutex to do i/o.Frasch
@Camise Yes .net 4.0 is an option. Why would you suggest that?Quartana
maximum number of 6 within your specific domain? well not that good idea either. you will not gain a significant performance-boost - if that's what you are after!Frasch
@Andreas aha I see. But let me add this than: the chance that a file is being written to is small, so most of the time the thread doesn't have to wait, only when a file is locked in the way I described.Quartana
@Quartana see my answer below...Camise
@Andreas No, I'm not after a performance boost. I'd like to use the construct to avoid 'file is locked' exceptions due to my threads doing something with the file. When outside processed are locking the file, I'm fine with an 'file is locked' exceptionQuartana
@Quartana see my anser below :)Frasch
V
4

I ran into the same problem with many threads that can write in the same file.

The one of the reason that mutex not good because it slowly:

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

BlockingCollection collection - very good idea, but for my case with rare collisions, parallel writes better than serial writes. Also way with dictionary much more easy to realise.

I use this solution (UPDATED):

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);
            }
        }
    }
}
Vd answered 14/3, 2013 at 17:36 Comment(1)
I think there is a problem with removing the entry from the dictionary. What if three clients call the method: the first creates the key and acquires the lock, the second gets the existing key and waits for the lock, the first finishes and removes the key and releases the lock, the second can start, a third comes in and creates a new key and acquires a new lock before the second has finished. Second and third are now running simultaneously.Straighten
C
3

Since you are talking about a producer-consumer situation with multiple threads the "standard solution would be to use BlockingCollection which is part of .NET 4 and up - several links with information:

IF you just want to make the locking process work then:

use a ConcurrentDictionary in combination with the TryAdd method call... if it returns true then the file was not "locked" and is now "locked" so the thread can proceed - and "unlock" it by calling Remove at the end... any other thread gets false in the meantime and can decide what to do...

I would definitely recommend the BlockingCollection approach though!

Camise answered 31/1, 2012 at 10:22 Comment(1)
dah ... bet me by 4 secs ... :)Frasch
F
1

An alternative would be: make one consumer thread which works on a queue, and blocks if it is empty. You can have several producer threads adding several filepaths to this queue and inform the consumer.

Since .net 4.0 there's a nice new class: System.Collections.Concurrent.BlockingCollection<T>

A while ago I had the same issue here on Stack Overflow - How do I implement my own advanced Producer/Consumer scenario?

Frasch answered 31/1, 2012 at 10:22 Comment(0)
P
-1

For those such as myself who stumble across this thread in the future - the answer given by @gabba works fantastically (tested with accessing the same file from 2048 concurrent threads and performing writing, reading, and file deletion), but if counting the references is not important, the following code:

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

can be reduced to:

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

The result is an empty object for each file path - i.e. a "named" lock. Instead of removing the lock object from the dictionary, it is used by the lock statement as a mutex for all threads that access this file path.

Prosser answered 21/1 at 13:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.