zoukankan      html  css  js  c++  java
  • 转载 互斥体与互锁 <第五篇>

     互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”。因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。

      任何线程在进入临界区之前,必须获取(acquire)与此区域相关联的互斥体的所有权。如果已有另一线程拥有了临界区的互斥体,其他线程就不能再进入其中。这些线程必须等待,直到当前的属主线程释放(release)该互斥体。什么时候需要使用互斥体呢?互斥体用于保护共享的易变代码,也就是,全局或静态数据。这样的数据必须通过互斥体进行保护,以防止它们在多个线程同时访问时损坏。

    1、Mutex

      Mutex是一个同步基元,它与前面提到的锁最大的区别在于它支持进程间同步。

      Mutex允许同一个线程多次重复访问共享区,但是对于别的线程那就必须等待,它甚至支持不同进程中的线程同步,这点更能体现他的优势,但是劣势也是显而易见的,那就是巨大的性能损耗和容易产生死锁的困扰,所以除非需要在特殊场合,否则 我们尽量少用为妙,这里并非是将Mutex的缺点说的很严重,而是建议大家在适当的场合使用更为适合的同步方式,Mutex 就好比一个重量型的工具,利用它则必须付出性能的代价。

      1、Mutex线程同步

      Mutex实现线程同步主要依靠以下两个方法实现:

    • WaitOne 阻止当前线程,直到当前 WaitHandle 收到信号。
    • ReleaseMutex 释放 Mutex 一次。
    复制代码
        class Program
        {
            static void Main(string[] args)
            {
                for (int i = 0; i < 10; i++)
                {
                    ThreadPool.QueueUserWorkItem(Run);
                }
                Console.ReadKey();
            }
    
            static int count = 0;
            static Mutex mutex = new Mutex();
            static void Run(object obj)
            {
                //阻止当前线程(如果去掉这两行)
                mutex.WaitOne(); 
                Console.WriteLine("当前数字:{0}", ++count);
                //释放 Mutex (如果去掉这行)
                mutex.ReleaseMutex();
            }
        }
    复制代码

      输出:

      

      如果去掉则输出如下:

      

      Mutex和Monitor的区别:

    1. Monitor不是waitHandle的子类,它具有等待和就绪队列的实际应用;
    2. Monitor无法跨进程中实现线程同步,但是Mutex可以;
    3. 相对而言两者有明显的性能差距,mutex相对性能较弱但是功能更为强大,monitor则性能比较好;
    4. 两者都是用锁的概念来实现同步不同的是monitor一般在方法(函数)调用方加锁;mutex一般在方法(函数)内部加锁,即锁定被调用端;
    5. Monitor和Lock多用于锁定被调用端,而Mutex则多用锁定调用端。

      2、进程间同步

      当给Mutex取名的时候能够实现进程同步,不取名实现线程同步。

      Mutex有两种类型:未命名的局部mutex和已命名的系统mutex。

    • 本地mutex仅存在与进程当中,进程内可见;
    • 已命名的系统mutex在整个操作系统中可见,可用于同步进程活动;
    复制代码
        class Program
        {
            static void Main(string[] args)
            {
                //使用线程输出等待状态
                Thread t1 = new Thread(ShowMyWord);
                t1.Start();
    
                Run(t1);
    
                Console.Read();
            }
    
            static int count = 0;
            static Mutex mutex = new Mutex(false, "xxoo");
         static void Run(Thread t1)
            {
                //这个WaitOne方法要么返回true,要么一直不返回(不会返回false),所以没办法用if来判断
                //于是,用个线程输出等待状态
                mutex.WaitOne();
                Console.WriteLine("终于轮到老子了!  " + DateTime.Now.TimeOfDay.ToString());
                //停止线程t1,不要再输出等待状态
                t1.Abort();
                //模拟干活十秒
                Thread.Sleep(10000);
                Console.WriteLine("干完!  " + DateTime.Now.TimeOfDay.ToString());
                //释放 Mutex
                mutex.ReleaseMutex();
            }
    
            static void ShowMyWord(object obj)
            {
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(2000);
                    Console.WriteLine("我的心在等待,一直在等待!   " + DateTime.Now.TimeOfDay.ToString());
                }
            }
        }
    复制代码

      以上代码,将生成的.exe文件复制两份:

      

      快速运行两个输出如下:

      

      3、互斥体控制控制台程序仅能启动一次

      由于Mutex能够用于进程间同步,因此我们可以很轻易地利用它实现控制程序只能启动一次的效果。

    复制代码
        static void Main(string[] args)
        {
            bool IsFirstCreate;
            Mutex instance = new Mutex(true, "NewApplication", out IsFirstCreate);
            if (IsFirstCreate) //赋予了线程初始所属权,也就是首次使用互斥体 
            {
                instance.ReleaseMutex();
            }
            else
            {
                Console.WriteLine("你已经启动了一个程序,本程序将于5秒后自动退出!");
                Thread.Sleep(5000);
                return;
            }
            Console.WriteLine("程序启动成功!");
            Console.ReadKey();
        }
    复制代码

      输出如下:

      

      4、控制WinForm程序只能启动一次

    复制代码
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            bool IsFirstRun;
            Mutex instance = new Mutex(true, "xxx", out IsFirstRun);
            if (IsFirstRun) //赋予了线程初始所属权,也就是首次使用互斥体 
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
                instance.ReleaseMutex();
            }
            else
            {
                MessageBox.Show("你已启动了一个程序,本程序将于5秒后退出", "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Thread.Sleep(5000);
                Application.Exit();
            }
        }
    }
    复制代码

      输出如下:

       

      第3、4测试均需要在bin目录下直接启动多个.exe。

    二、Interlocked

       实际引用中,可能我们对共享变量的使用并不十分复杂,可能只是一些简单的操作如:自增、自减、求和、赋值、比较等。

       MSDN中的解析Interlocked为多个线程共享的变量提供原子操作。

      常用操作如下:

    方法 说明
    Add 相加
    CompareExchange 比较
    Increment 递增
    Decrement 递减
    Exchange 赋值

       示例:

    复制代码
       class Program
        {
            static void Main(string[] args)
            {
                for (int i = 0; i < 20; i++)
                {
                    Thread t = new Thread(Run);
                    t.Start();
                }
    
                Console.Read();
            }
    
            static int Incre = 0;
            static int Add = 0;
            static int Exchange = 0;
            static int Decre = 21;
            static int CompareExchange = 0;
    
            static Mutex mutex = new Mutex();
    
            static void Run()
            {
                //自增操作
                //Console.WriteLine("当前数字:{0}", Interlocked.Increment(ref Incre));
                //递减操作
                //Console.WriteLine("当前数字:{0}", Interlocked.Decrement(ref Decre));
                //相加
                //Console.WriteLine("当前数字:{0}", Interlocked.Add(ref Add,10));
                //赋值
                //Console.WriteLine("当前数字:{0}", Interlocked.Exchange(ref Exchange, 5));
                //比较,如果第三个参数等于CompareExchange,则将第二个参数的值,赋给第一个参数
                Console.WriteLine("当前数字:{0}", Interlocked.CompareExchange(ref CompareExchange, 15,0));
                
            }
        }
    复制代码
  • 相关阅读:
    Java读取Excel文件的几种方法
    PowerDesigner的安装和数据库创建(转载)
    JAVA UUID 生成
    Mysql的“Limit”操作
    实现java 中 list集合中有几十万条数据,每100条为一组取出
    MySQL5中大数据错误:Packet for query is too large (****** > ******). You can change this value on the server by setting the max_allowed_packet' variable.;
    Mybatis 示例之 foreach
    JAVA WEB ------ 文件下载及导出数据到office Execl表格
    8 -- 深入使用Spring -- 4...3 AOP的基本概念
    8 -- 深入使用Spring -- 4...2 使用AspectJ实现AOP
  • 原文地址:https://www.cnblogs.com/Jeely/p/10750724.html
Copyright © 2011-2022 走看看