C#互斥鎖初探


一、前言

  互斥鎖用於保護臨界資源,本文是在對linux中的互斥鎖有了一定理解之后再對C#中的互斥鎖進行分析,互斥鎖的作用以及linux中的互斥鎖請看我這篇博客https://www.cnblogs.com/Suzkfly/p/14363619.html

  本文是在查閱了一些網上的資料,以及自己對官方的Mutex類和WaitHandle類的理解的情況下寫出的,由於本人也是初學,可能會有不正確的情況,還請指正。

  互斥鎖的幾個基本操作:初始化鎖、上鎖、解鎖、銷毀鎖,但在C#里好像不需要銷毀鎖(沒查到相關資料)。

  互斥鎖在多線程里使用才有意義,關於多線程的用法,參閱我寫的這篇文章:https://www.cnblogs.com/Suzkfly/p/15840584.html

二、引出需求

  先看一段代碼:

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);             //模擬復雜的計算過程
                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);             //模擬復雜的計算過程
                a++;
                Console.WriteLine("Main a = {0}", a);
            }
        }
    }
}

  這個程序在Main方法以及test方法中都使用到了變量a,我們希望,在Main方法中開始讓a=1,然后經過一段時間讓a加1,那么a的值就是2,所以在Main方法中,我們希望a的值是1,2,1,2...這樣交替的,同理在test方法中我們希望a的值是3,4,3,4...交替,但是運行結果如下:

  

   從結果看出,Main線程在遇到第一個Sleep時線程就睡眠了,這時就轉到test線程中去執行,a的值變為了3,test線程遇到Sleep也睡眠了,1S過后Main線程蘇醒了,此時它想讓a自加,但是此時a的值已經被test線程變為了3,所以Main線程中a自加之后a的值就變為了4,這就是上述程序運行的結果,這個結果並不是我們想要的。a這個變量就是臨界資源,我們希望在一個線程在使用臨界資源時,不會被別的線程打斷,這時就可以使用互斥鎖。

三、簡單用法

  使用互斥鎖需要引用的命名空間同多線程一樣,都是System.Threading。

  互斥鎖的類名為“Mutex”,轉到Mutex的定義如下圖:

  

   而Mutex又是繼承自WaitHandle:

  

   再往上的父類就不探究了。互斥鎖由易到難一點點的說。

  首先可以這樣創建一個互斥鎖:

Mutex mutex = new Mutex();

  這樣去獲得鎖的權限:

mutex.WaitOne();

  這樣去釋放鎖:

mutex.ReleaseMutex();

  將上一節的代碼稍微改一下,變成下面這個樣子:

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);             //模擬復雜的計算過程
                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);             //模擬復雜的計算過程
                a++;
                Console.WriteLine("Main a = {0}", a);
                mutex.ReleaseMutex();
            }
        }
    }
}

  運行結果如下:

  

   這樣才是我們想要的結果。

Mutex(bool initiallyOwned);

  這個構造方法用於指示調用線程是否應具有互斥體的初始所有權,如果給調用線程賦予互斥體的初始所屬權,則傳入的參數為 true;否則為 false。也就是說,如果傳入true,那么別的線程是獲取不到鎖的,除非本該線程調用ReleaseMutex()。下面兩句代碼效果是等同的:

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

C#中的互斥鎖是哪種鎖

  linux中有4種鎖,分別為:普通鎖、檢錯鎖、嵌套鎖和適應鎖,其中普通鎖和適應鎖是一樣的(我認為是一樣的),那么C#中的互斥鎖是哪種鎖呢,首先看看它是不是普通鎖,為此寫出下列代碼:

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()這個方法是去獲取鎖資源,如果獲取成功,那么返回true,否則用不返回,在while循環里只調用WaitOne()去獲得鎖資源,而不釋放,如果是普通鎖,那么程序將打印一次“ret = true”,而實際運行結果如下:

  

   程序一直能打印出“ret = true”,這意味着該線程能一直獲得鎖,那么就能夠排除它是普通鎖的可能。

  接下來這段代碼能驗證它是不是嵌套鎖:

using System;
using System.Threading;

namespace mutex_test
{
    class Program
    {
        public static Mutex mutex = new Mutex(true);    //主線程具有初始所有權

        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();   //由於線程具有初始所有權,所以這里應該多釋放一次
                Console.WriteLine("Main ReleaseMutex");
                Thread.Sleep(1000);
            }
            while (true)
            {
                Thread.Sleep(1000);
            }
        }
    }
}

代碼分析:

  Main方法中獲取了3次鎖資源而不釋放,之后釋放4次鎖資源,如果這個鎖是嵌套鎖,那么等4次鎖資源都釋放完畢之后,test線程才能夠獲得鎖資源,運行結果如下:

  

   這個結果說明,確實是在Main線程釋放了4次鎖資源之后,test線程才獲得了鎖的所有權,說明C#中用Mutex構造出來的鎖對應的是linux中的嵌套鎖

Mutex(bool initiallyOwned, string name);

   這個構造方法用於給互斥鎖命名,如果傳入null則構造出的互斥鎖也是未命名的。之前用Mutex();或者Mutex(bool initiallyOwned);構造出來的互斥鎖都是沒有名字的。既然有了命名的功能,那么如果在不同的線程中構造出相同名字的互斥鎖會怎么樣呢?請看下面的代碼:

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

  代碼分析:

  在Main方法和test方法中都構造一個名為“MyMutex”的互斥鎖,並且都給初始權限,在各自的while循環中都去獲得鎖的權限,然后釋放,因為之前驗證過,C#中的互斥鎖是嵌套鎖,所以在線程已經擁有鎖權限的時候仍然可以用WaitOne()去獲得權限,如果兩個線程構造出來的鎖是不同的鎖,那么兩個線程都可以打印出各自的ret值,運行結果如下:

  

 

   這說明,test線程其實並沒有獲得鎖的所有權,如果把代碼第25行中的true改為false,那么兩個線程才能夠交替打印ret的值說明,如果使用Mutex(bool initiallyOwned, string name);方法去構造一個互斥鎖,並且如果已經具有相同名字的互斥鎖存在,那么無論構造時傳入的initiallyOwned是true還是false,該線程都不具備互斥鎖的所有權,但它仍然可以使用該互斥鎖

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

  為了知道自己構造出來的互斥鎖是不是已經存在,可以再傳入createdNew參數,如果鎖存在,那么createdNew的變為false,否則為true。代碼如下:

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

  運行結果:

  

 

   該程序說明,test線程試圖構造一個名為“MyMutex”的互斥鎖時,發現這把鎖已經存在了,所以is_new的值被賦成了false。

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

  這個構造方法多了一個mutexSecurity,從名字上能看出,它與鎖的安全性有關,MutexSecurity類我沒有接觸過,但是在網上找到一篇具有參考價值的文章:https://bbs.csdn.net/topics/280051957,我把他的代碼稍微改了一下,並結合互斥鎖的程序得到下面的代碼:

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);    //只是將WorldSid改成了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();
            }
        }
    }
}

  這份代碼在程序第14行會拋出一個異常,異常類型為:System.UnauthorizedAccessException,這說明“命名互斥體存在且具有訪問控制安全性,但用戶不具備 System.Security.AccessControl.MutexRights.FullControl”,除非將第28行的NullSid改為WorldSid,這時不會拋出異常,但test線程得到的鎖並不是一把新鎖。這個例子說明,可以通過傳入mutexSecurity參數來限制其他線程(也不一定是線程)的權限。

Mutex OpenExisting(string name);

  這個方法的作用在官方注釋里寫的是“打開指定的已命名的互斥體”,實際上它是用來得到一個已經存在的Mutex對象,由於它是靜態方法,因此不需要通過對象去調用。測試代碼如下:

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

  在第11行去得到一個已經存在的互斥鎖對象,如果指定名字的互斥鎖是已經存在的,那么它與下面這一句效果是一樣的:

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

  但是如果指定名字的互斥鎖不存在,那么調用Mutex OpenExisting(string name);方法時會拋出異常。

bool TryOpenExisting(string name, out Mutex result);

  這個方法試圖去得到一個已經存在的互斥鎖對象,如果成功,那么返回true,並且result被賦值為該互斥鎖,否則返回false,不會拋出異常。測試程序如下:

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

  

  最后Mutex類里還有幾個方法,研究了一下沒研究出來,暫時先放放。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM