Net Core教程

C#互斥锁初探

本文主要是介绍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类里还有几个方法,研究了一下没研究出来,暂时先放放。

这篇关于C#互斥锁初探的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!