• 【holm】C#线程监视器Monitor类使用指南


    线程的同步

    概念

    线程同步是指控制多个线程的相对执行顺序,避免在使用共享资源时可能出现的问题。

    线程同步可用的方法

    • 轮询(不推荐):通过反复检查Thread类IsAlive属性判断调用状态。
    • Thread.Join():将一个线程加入到本线程中,本线程的执行会等待另一线程执行完毕。适合管理少量线程,不适用于复杂情况。
    • lock语句(Monitor类)

    Monitor概述

    Monitor类主要用于防止多个线程同时操作一个对象产生冲突。 (无法实例化)
    主要功能:

    • 可以从任何上下文调用
    • 加锁:当第一个线程访问加锁资源时,此资源会被锁住,其他想要访问此资源的线程进入锁等待状态,直到第一个线程结束访问。
    • 解锁
      涉及操作有:lock语句/Monitor.Enter()/Monitor.Exit()/Monitor.Wait()/Monitor.Pulse()/Monitor.PulseAll()

    对于每个同步对象来维护以下信息:

    • 对当前拥有锁的线程的引用。
    • 对就绪的队列,其中包含就可以获得的锁的线程的引用。
    • 对等待队列,其中包含有关的锁定的对象的状态更改通知等待的线程的引用。

    Monitor将锁定对象(即引用类型)而非值类型。 在您将值类型传递给 EnterExit 时,它会针对每个调用分别装箱。 由于每个调用都创建一个单独的对象,所以 Enter 从不拦截,并且其旨在保护的代码并未真正同步。 此外,传递给 Exit 的对象不同于传递给 Enter 的对象,所以 Monitor 将引发 SynchronizationLockException,并显示消息“从不同步的代码块中调用了对象同步方法”。

    lock语句 /Monitor.Enter()/Monitor.Exit()

    语法:

    lock(对象或表达式)//引用类型,可用this,静态类使用typeof()
    {
        ...
    }
    

    当程序进入lock区域时,首先获取指定对象的互斥锁(类似Threading命名空间下的Mutex的作用),此语句执行期间给指定对象上锁,语句执行完成后进行解锁。
    等同于:

    System.Threading.Monitor.Enter(对象或表达式);
    try{
        ...
    }
    finally{
        System.Threading.Monitor.Exit(对象或表达式);
    }
    

    以下是一个实例,每个进程分别让x,y自增并打印自增后的值。如果不加锁的话x,y的值可能不同。

        class Program
        {
            static int x = 0, y = 0;
            public static void Main(string[] args)
            {
    
                Thread thread1 = new Thread(TestEqual);
                Thread thread2 = new Thread(TestEqual);
                thread1.Start();
                thread2.Start();
            }
            static void TestEqual()
            {
                for (int i = 0; i < 5; i++)
                {
                    //lock (typeof(Program))
                    {
                        x++;
                        Thread.Sleep(new Random().Next(10));
                        y++;
                        Console.WriteLine($"x={x},y={y}");
                    }
                }
            }
        }
        //运行结果:
        //x=2,y=1
        //x=2,y=2
        //x=4,y=4
        //x=4,y=4
        //x=6,y=6
        //x=6,y=6
        //x=8,y=8
        //x=8,y=8
        //x=10,y=10
        //x=10,y=9
    

    此外:在锁内也可被Thread.Interrupt()中断,将会引发ThreadInterruptedException异常,在另一进程中使用中断进程的Join()方法恢复运行。

    Monitor.Wait()/Monitor.Pulse()/Monitor.PulseAll()

    • Monitor.Wait():释放对象上的锁并阻止当前线程,直到它重新获取该锁。
    • Monitor.Pulse()/Monitor.PulseAll():当前拥有指定对象的锁的线程调用此方法,以向第一个线程发出锁。 接收到脉冲后,等待线程会移动到就绪队列。 如果调用的线程Pulse释放该锁,则就绪队列(不一定是发出脉冲的线程)中的下一个线程将获取该锁。如果调用的线程PulseAll释放该锁,则就绪队列中的所有线程将依次获取该锁。

    注意:

    • 必须从同步的代码块内调用 PulsePulseAllWait 方法。
    • 若要向多个线程发出信号,请使用 PulseAll 方法。

    实例:

       class Program
        {
            public static void Main(string[] args)
            {
                Thread thread1 = new Thread(Test);
                thread1.Name = "thread1";
                Thread thread2 = new Thread(Test);
                thread2.Name = "thread2";
                Thread pulseThread = new Thread(Pulse);
                thread1.Start();
                thread2.Start();
                pulseThread.Start();
            }
            private static void Pulse()
            {
                lock (typeof(Program))
                {
                    Thread.Sleep(1500);
                    x++;
                    Monitor.Pulse(typeof(Program));//唤醒先进入就绪队列的进程
                    //Monitor.PulseAll(typeof(Program)); //唤醒全部就绪队列的进程
                }
            }
            static void Test()
            {
                lock (typeof(Program))
                {
                    Monitor.Wait(typeof(Program));
                    Thread.Sleep(400);
                    Console.WriteLine($"Thread name:{Thread.CurrentThread.Name}");
                }
            }
        }
    

    Monitor.TryEnter()/Monitor.IsEntered()

    • 使用Monitor.IsEntered()确定当前线程是否保留指定对象上的锁。
    • Monitor.TryEnter()类似于Enter但它永远不会阻止当前线程。如果该线程不能输入而不会阻塞,则该方法将返回false,和线程不进入关键节。

    参考资料

  • 相关阅读:
    JStorm集群的安装和使用
    Kafka集群的安装和使用
    Linux下which、whereis、locate、find 命令的区别
    Linux 命令小记
    Linux 普通进程 后台进程 守护进程
    Java 命令行运行参数大全
    一台机子上运行使用不同Java版本的多个tomcat
    Ubuntu 设置程序开机启动(以指定用户身份)
    linux 开机启动过程详解
    关于Linux发行版的选择
  • 原文地址:https://www.cnblogs.com/holm/p/12845595.html
Copyright © 2020-2023  润新知