On C# mutual exclusion lock

1, Foreword

Mutex is used to protect critical resources. This paper analyzes mutex in C# after having a certain understanding of mutex in linux. Please see my blog for the role of mutex and mutex in linux https://www.cnblogs.com/Suzkfly/p/14363619.html

This article is written after consulting some online materials and my understanding of the official Mutex class and WaitHandle class. Since I am also a beginner, there may be incorrect situations. Please correct me.

Several basic operations of mutex lock: initializing lock, locking, unlocking and destroying lock, but it seems that there is no need to destroy lock in C# (no relevant information is found).

Mutex is meaningful only when used in multithreading. For the usage of multithreading, please refer to this article I wrote: https://www.cnblogs.com/Suzkfly/p/15840584.html

2, Elicit requirements

Let's start with a code:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        public static int a = 0;

        public static void test()
        {
            while (true)
            {
                a = 3;
                Console.WriteLine("test a = {0}", a);
                Thread.Sleep(1000);             //Simulate complex calculation process
                a++;
                Console.WriteLine("test a = {0}", a);
            }
        }

        static void Main(string[] args)
        {
            Thread thread = new Thread(new ThreadStart(test));
            thread.Start();

            while (true)
            {
                a = 1;
                Console.WriteLine("Main a = {0}", a);
                Thread.Sleep(1000);             //Simulate complex calculation process
                a++;
                Console.WriteLine("Main a = {0}", a);
            }
        }
    }
}

This program uses the variable a in both the Main method and the test method. We want to start making a=1 in the Main method, and then add 1 to a after a period of time, so the value of a is 2. Therefore, in the Main method, we want the value of a to be 1,2,1,2 In the same way, in the test method, we want the value of a to be 3,4,3,4 Alternately, but the operation results are as follows:

  

It can be seen from the results that when the Main thread encounters the first Sleep, the thread goes to Sleep. At this time, it goes to the test thread for execution. The value of a changes to 3, and the test thread also sleeps when it encounters Sleep. After 1S, the Main thread wakes up. At this time, it wants a to add itself, but at this time, the value of a has been changed to 3 by the test thread, so the value of a in the Main thread changes to 4 after a adds itself, This is the result of the above program. This result is not what we want. A this variable is the critical resource. We hope that when a thread uses the critical resource, it will not be interrupted by other threads. At this time, we can use mutex.

3, Simple usage

Like multithreading, the namespace that needs to be referenced when using mutex is system Threading.

The class name of Mutex is "Mutex", and the definition of Mutex is as follows:

  

Mutex is inherited from WaitHandle:

  

The upper parent class will not be explored. Mutex lock from easy to difficult to say a little bit.

First, you can create a mutex like this:

Mutex mutex = new Mutex();

To obtain the permission of the lock:

mutex.WaitOne();

To release the lock:

mutex.ReleaseMutex();

Change the code in the previous section slightly to look like this:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        public static int a = 0;
        public static Mutex mutex = new Mutex();

        public static void test()
        {
            while (true)
            {
                mutex.WaitOne();
                a = 3;
                Console.WriteLine("test a = {0}", a);
                Thread.Sleep(1000);             //Simulate complex calculation process
                a++;
                Console.WriteLine("test a = {0}", a);
                mutex.ReleaseMutex();
            }
        }

        static void Main(string[] args)
        {
            Thread thread = new Thread(new ThreadStart(test));
            thread.Start();

            while (true)
            {
                mutex.WaitOne();
                a = 1;
                Console.WriteLine("Main a = {0}", a);
                Thread.Sleep(1000);             //Simulate complex calculation process
                a++;
                Console.WriteLine("Main a = {0}", a);
                mutex.ReleaseMutex();
            }
        }
    }
}

The operation results are as follows:

  

This is the result we want.

Mutex(bool initiallyOwned);

This construction method is used to indicate whether the calling thread should have the initial ownership of the mutex. If the calling thread is given the initial ownership of the mutex, the passed parameter is true; Otherwise, it is false. In other words, if true is passed in, other threads cannot obtain the lock unless this thread calls ReleaseMutex(). The following two sentences have the same effect:

Mutex mutex = new Mutex(false);
Mutex mutex = new Mutex();

What kind of lock is a mutex in C #

There are four kinds of locks in linux: ordinary lock, error detection lock, nested lock and adaptive lock. The ordinary lock and adaptive lock are the same (I think they are the same). So what kind of lock is the mutex lock in C#? First, check whether it is an ordinary lock. For this purpose, write the following code:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        static void Main(string[] args)
        {
            Mutex mutex = new Mutex();
            bool ret;

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("ret = {0}", ret);
                Thread.Sleep(1000);
            }
        }
    }
}

WaitOne() is used to obtain the lock resource. If it is obtained successfully, it returns true. Otherwise, it does not return. In the while loop, only WaitOne() is called to obtain the lock resource without releasing it. If it is an ordinary lock, the program will print "ret = true" once, and the actual operation results are as follows:

  

The program can always print "ret = true", which means that the thread can always obtain the lock, so it can rule out the possibility that it is an ordinary lock.

The following code can verify whether it is a nested lock:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        public static Mutex mutex = new Mutex(true);    //The main thread has initial ownership

        public static void test()
        {
            while (true)
            {
                mutex.WaitOne();
                Console.WriteLine("test");
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }

        static void Main(string[] args)
        {
            Thread thread = new Thread(new ThreadStart(test));
            thread.Start();

            for (int i = 0; i < 3; i++)
            {
                mutex.WaitOne();
                Console.WriteLine("Main");
                Thread.Sleep(1000);
            }
            for (int i = 0; i < 4; i++)
            {
                mutex.ReleaseMutex();   //Since the thread has initial ownership, it should be released once more
                Console.WriteLine("Main ReleaseMutex");
                Thread.Sleep(1000);
            }
            while (true)
            {
                Thread.Sleep(1000);
            }
        }
    }
}

Code analysis:

In the Main method, the lock resources are obtained three times without releasing, and then released four times. If the lock is a nested lock, the test thread can obtain the lock resources only after all four lock resources are released. The running results are as follows:

  

This result shows that the test thread did not get the ownership of the lock until the Main thread released the lock resources four times. It shows that the lock constructed with Mutex in C # corresponds to the nested lock in linux.

Mutex(bool initiallyOwned, string name);

This construction method is used to name the mutex. If null is passed in, the constructed mutex is also unnamed. Mutex() was used before; Or mutex (bool initially owned); The mutexes constructed have no names. Now that you have the naming function, what happens if you construct a mutex with the same name in different threads? See the following code:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        public static void test()
        {
            bool ret;
            Mutex mutex = new Mutex(true, "MyMutex");

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("test ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }

        static void Main(string[] args)
        {
            bool ret;
            Mutex mutex = new Mutex(true, "MyMutex");

            Thread thread = new Thread(new ThreadStart(test));
            thread.Start();

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("Main ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }
    }
}

Code analysis:

A mutex named "MyMutex" is constructed in both the Main method and the test method, and the initial permission is given. The permission of the lock is obtained in their respective while loops, and then released. As previously verified, the mutex in C# is a nested lock, so the permission can still be obtained with WaitOne() when the thread already has the lock permission, If the locks constructed by two threads are different locks, both threads can print their respective ret values. The running results are as follows:

  

 

This shows that the test thread does not actually obtain the ownership of the lock. If the true in line 25 of the code is changed to false, the two threads can print the ret value alternately. Note: if Mutex(bool initiallyOwned, string name) is used; Method to construct a mutex. If a mutex with the same name already exists, no matter whether the initiallyOwned passed in during construction is true or false, the thread does not have the ownership of the mutex, but it can still use the mutex.

Mutex(bool initiallyOwned, string name, out bool createdNew);

In order to know whether the mutually exclusive lock constructed by yourself already exists, you can pass in the createdNew parameter. If the lock exists, the createdNew will become false, otherwise it will be true. The code is as follows:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        public static void test()
        {
            bool ret;
            bool is_new;
            Mutex mutex = new Mutex(true, "MyMutex", out is_new);
            Console.WriteLine("test is_new = {0}", is_new);

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("test ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }

        static void Main(string[] args)
        {
            bool ret;
            bool is_new;
            Mutex mutex = new Mutex(false, "MyMutex", out is_new);
            Console.WriteLine("Main is_new = {0}", is_new);

            Thread thread = new Thread(new ThreadStart(test));
            thread.Start();

            Thread.Sleep(1000);

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("Main ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }
    }
}

Operation results:

  

 

The program shows that when the test thread tries to construct a mutex named "MyMutex", it finds that the lock already exists, so it is_ The value of new is set to false.

Mutex(bool initiallyOwned, string name, out bool createdNew, MutexSecurity mutexSecurity);

This construction method has one more mutexsecurity. From the name, it is related to the security of locks. I haven't contacted mutexsecurity class, but I found an article of reference value on the Internet: https://bbs.csdn.net/topics/280051957 , I changed his code a little and combined it with the mutex program to get the following code:

using System;
using System.Threading;
using System.Security.AccessControl;
using System.Security.Principal;

namespace mutex_test
{
    class Program
    {
        public static void test()
        {
            bool ret;
            bool is_new;
            Mutex mutex = new Mutex(true, "MyMutex", out is_new);
            Console.WriteLine("test is_new = {0}", is_new);

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("test ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }

        static void Main(string[] args)
        {
            SecurityIdentifier security_identifier = new SecurityIdentifier(WellKnownSidType.NullSid, null);    //Just changed WorldSid to NullSid
            MutexAccessRule rule = new MutexAccessRule(security_identifier, MutexRights.FullControl, AccessControlType.Allow);
            MutexSecurity mutexSecurity = new MutexSecurity();
            mutexSecurity.AddAccessRule(rule);

            bool ret;
            bool is_new;
            Mutex mutex = new Mutex(false, "MyMutex", out is_new, mutexSecurity);
            Console.WriteLine("Main is_new = {0}", is_new);

            Thread thread = new Thread(new ThreadStart(test));
            thread.Start();

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("Main ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }
    }
}

This code will throw an exception on line 14 of the program. The exception type is: system Unauthorizedaccessexception, which indicates that "the named mutex exists and has access control security, but the user does not have System.Security.AccessControl.MutexRights.FullControl". Unless the NullSid in line 28 is changed to WorldSid, no exception will be thrown at this time, but the lock obtained by the test thread is not a new lock. This example shows that the permissions of other threads (not necessarily threads) can be restricted by passing the mutexSecurity parameter.

Mutex OpenExisting(string name);

The function of this method is to "open the specified named Mutex" in the official comment. In fact, it is used to get an existing Mutex object. Because it is a static method, it does not need to be called through the object. The test code is as follows:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        public static void test()
        {
            bool ret;
            Mutex mutex = Mutex.OpenExisting("MyMutex");

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("test ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }

        static void Main(string[] args)
        {
            bool ret;
            bool is_new;
            Mutex mutex = new Mutex(true, "MyMutex", out is_new);
            Console.WriteLine("Main is_new = {0}", is_new);

            Thread thread = new Thread(new ThreadStart(test));
            thread.Start();

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("Main ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }
    }
}

In line 11, get an existing mutex object. If the mutex with the specified name already exists, it will have the same effect as the following sentence:

Mutex mutex = new Mutex(true, "MyMutex", out is_new);

However, if the Mutex with the specified name does not exist, Mutex OpenExisting(string name) will be called; Method will throw an exception.

bool TryOpenExisting(string name, out Mutex result);

This method attempts to get an existing mutex object. If it succeeds, it returns true, and the result is assigned to the mutex. Otherwise, it returns false and will not throw an exception. The test procedure is as follows:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        public static void test()
        {
            bool ret;
            bool success;
            Mutex mutex;

            success = Mutex.TryOpenExisting("MyMutex", out mutex);
            Console.WriteLine("success = {0}", success);
            if (success)
            {
                while (true)
                {
                    ret = mutex.WaitOne();
                    Console.WriteLine("test ret = {0}", ret);
                    Thread.Sleep(1000);
                    mutex.ReleaseMutex();
                }
            }
        }

        static void Main(string[] args)
        {
            bool ret;
            bool is_new;
            Mutex mutex = new Mutex(true, "MyMutex", out is_new);
            Console.WriteLine("Main is_new = {0}", is_new);

            Thread thread = new Thread(new ThreadStart(test));
            thread.Start();

            while (true)
            {
                ret = mutex.WaitOne();
                Console.WriteLine("Main ret = {0}", ret);
                Thread.Sleep(1000);
                mutex.ReleaseMutex();
            }
        }
    }
}

  

Finally, there are several methods in Mutex class. I studied them and didn't find them. I'll put them first for the time being.

Added by TheJuice on Fri, 04 Feb 2022 05:25:36 +0200