ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C#互斥锁初探

2022-02-04 11:33:59  阅读:190  来源: 互联网

标签:C# 初探 ret 互斥 Mutex new test mutex


一、前言

  互斥锁用于保护临界资源,本文是在对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#,初探,ret,互斥,Mutex,new,test,mutex
来源: https://www.cnblogs.com/Suzkfly/p/15861487.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有