C# 多线程同步方法 之 Monitor.Wait 与 Monitor.Pulse

1、线程同步原理

组成:(以单个object为准)
    1、[持锁线程]    = locker             = 持锁中
    2、[就绪队列]    = ready queue   = 一等队列,队列中的线程将被挂起等待,系统会自动按顺序为线程分配对象的排他锁
    3、[等待队列]    = wait queue      = 二等队列,队列中的线程将被挂起等待,需要通过Pulse来控制向前

2、Monitor 类使用说明

C# 多线程同步方法 之 Monitor.Wait 与 Monitor.Pulse_第1张图片

3、测试例子 A

模拟效果:

       //线程A
    1  lock (locker)
       {
                          //线程B               //线程C
    2                     lock (locker)         lock (locker)
                          {                     {
    3     Monitor.Wait                              
    4                        Monitor.Pulse          
    5                     }                         
    6                                           }
    7  }                                        

    ----------------【第1步】 当前锁为闲置状态则可直接获得锁
    持锁线程:A
    就绪队列:
    等待队列:
    ----------------【第2步】 此时A线程已持锁,那BC则按顺序进入[就绪队列]排队
    持锁线程:A
    就绪队列:BC
    等待队列:
    ----------------【第3步】 A线程主动释放锁并转入[等待队列],此时的锁将腾出来给[就绪队列]中的第一个成员使用
    持锁线程:B
    就绪队列:C
    等待队列:A
    ----------------【第4步】 B线程发出指令,将[等待队列]中的第一个成员插入到[就绪队列]的第二位
    持锁线程:B
    就绪队列:CA
    等待队列:
    ----------------【第5步】 B线程结束自动释放锁,此时的锁将腾出来给[就绪队列]中的第一个成员使用
    持锁线程:C
    就绪队列:A
    等待队列:
    ----------------【第6步】 C线程结束自动释放锁,此时的锁将腾出来给[就绪队列]中的第一个成员使用
    持锁线程:A
    就绪队列:
    等待队列:
    ----------------【第7步】 A线程结束自动释放锁,此时的锁将腾出来给[就绪队列]中的第一个成员使用
    持锁线程:
    就绪队列:
    等待队列:

测试代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Class1
    {
        public static object locker = new object();
        public static Stopwatch stopwatch = new Stopwatch();
        public static int index = 1;
        public static int start = 0;

        public static void Main(string[] args)
        {
            stopwatch.Start();

            var tasks = new Task[]
            {
                Task.Run(delegate { A("A");}),
                Task.Run(delegate { B("B");}),
                Task.Run(delegate { C("C");}),
            };

            while (Console.ReadKey().Key == ConsoleKey.Enter)
            {
                start++;
                Console.WriteLine();
                Console.WriteLine($"【第{start}步】");
            }
        }
        public static void ConsoleWriteLine(string text) => Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} {stopwatch.ElapsedMilliseconds.ToString().PadLeft(5, ' ')}\t{index++.ToString().PadLeft(2, ' ')}\t{text}");
        public static void A(string tag)
        {
            SpinWait.SpinUntil(() => start >= 1);

            ConsoleWriteLine($"{tag}\t[进入就绪队列]");
            Monitor.Enter(locker);
            ConsoleWriteLine($"{tag}\t[获得锁]");

            SpinWait.SpinUntil(() => start >= 3);

            ConsoleWriteLine($"{tag}\t[进入等待队列]");
            if (Monitor.Wait(locker))
                ConsoleWriteLine($"{tag}\t[重新获得锁]");
            else
                ConsoleWriteLine($"{tag}\t[重新获得锁(超时)]");

            SpinWait.SpinUntil(() => start >= 7);

            ConsoleWriteLine($"{tag}\t[退出]");
            Monitor.Exit(locker);
        }
        public static void B(string tag)
        {
            SpinWait.SpinUntil(() => start >= 2);
            Thread.Sleep(00);

            ConsoleWriteLine($"{tag}\t[进入就绪队列]");
            Monitor.Enter(locker);
            ConsoleWriteLine($"{tag}\t[获得锁]");

            SpinWait.SpinUntil(() => start >= 4);

            ConsoleWriteLine($"{tag}\t[Pulse]");
            Monitor.Pulse(locker);

            SpinWait.SpinUntil(() => start >= 5);

            ConsoleWriteLine($"{tag}\t[退出]");
            Monitor.Exit(locker);
        }
        public static void C(string tag)
        {
            SpinWait.SpinUntil(() => start >= 2);
            Thread.Sleep(10);

            ConsoleWriteLine($"{tag}\t[进入就绪队列]");
            Monitor.Enter(locker);
            ConsoleWriteLine($"{tag}\t[获得锁]");

            SpinWait.SpinUntil(() => start >= 6);

            ConsoleWriteLine($"{tag}\t[退出]");
            Monitor.Exit(locker);
        }
    }
}

测试结果:

C# 多线程同步方法 之 Monitor.Wait 与 Monitor.Pulse_第2张图片

4、测试例子 B

模拟效果:

       //线程A                //线程B                //线程C       
    1  lock (locker)          lock (locker)         lock (locker)  
       {                      {                     {              
    2     Monitor.Wait(1000)     Monitor.Wait          
                                                                          //线程D                 //线程E
                                                                          lock (locker)           lock (locker)
                                                                          {                       {
    3                                                  Monitor.PulseAll
    4                                               }
    5  }                                                                  
    6                         }                                                                   }
    7                         
    8                                                                     }

    ----------------【第1步】
    持锁线程:A
    就绪队列:BC
    等待队列:
    ----------------【第2步】
    持锁线程:C
    就绪队列:DE
    等待队列:AB
    ----------------【第3步】 Pulse会将[等待队列]的成员插入到[就绪队列]的第二位
    持锁线程:C
    就绪队列:DABE
    等待队列:
    ----------------【第4步】
    持锁线程:D
    就绪队列:ABE
    等待队列:
    ----------------【第5步】
    持锁线程:A
    就绪队列:BE
    等待队列:
    ----------------【第6步】
    持锁线程:B
    就绪队列:E
    等待队列:
    ----------------【第7步】
    持锁线程:E
    就绪队列:
    等待队列:

测试代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Class1
    {
        public static object locker = new object();
        public static Stopwatch stopwatch = new Stopwatch();
        public static int index = 1;
        public static int start = 0;

        public static void Main(string[] args)
        {
            stopwatch.Start();

            var tasks = new Task[]
            {
                Task.Run(delegate { A("A");}),
                Task.Run(delegate { B("B");}),
                Task.Run(delegate { C("C");}),
                Task.Run(delegate { D("D");}),
                Task.Run(delegate { E("E");}),
            };

            while (Console.ReadKey().Key == ConsoleKey.Enter)
            {
                start++;
                Console.WriteLine();
                Console.WriteLine($"【第{start}步】");
                Thread.Sleep(500); //为避免速度过快而导致步骤错乱
            }
        }
        public static void ConsoleWriteLine(string text) => Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} {stopwatch.ElapsedMilliseconds.ToString().PadLeft(5, ' ')}\t{index++.ToString().PadLeft(2, ' ')}\t{text}");
        public static void A(string tag)
        {
            SpinWait.SpinUntil(() => start >= 1);
            Thread.Sleep(00);

            ConsoleWriteLine($"{tag}\t[进入就绪队列]");
            Monitor.Enter(locker);
            ConsoleWriteLine($"{tag}\t[获得锁]");

            SpinWait.SpinUntil(() => start >= 2);
            Thread.Sleep(00);

            ConsoleWriteLine($"{tag}\t[进入等待队列]");
            if (Monitor.Wait(locker, 3000))
            {
                ConsoleWriteLine($"{tag}\t[重新获得锁]");
                SpinWait.SpinUntil(() => start >= 6);
            }
            else
            {
                ConsoleWriteLine($"{tag}\t[重新获得锁(超时)]");
                SpinWait.SpinUntil(() => start >= 8);
            }

            ConsoleWriteLine($"{tag}\t[退出]");
            Monitor.Exit(locker);
        }
        public static void B(string tag)
        {
            SpinWait.SpinUntil(() => start >= 1);
            Thread.Sleep(10);

            ConsoleWriteLine($"{tag}\t[进入就绪队列]");
            Monitor.Enter(locker);
            ConsoleWriteLine($"{tag}\t[获得锁]");

            SpinWait.SpinUntil(() => start >= 2);
            Thread.Sleep(20);

            ConsoleWriteLine($"{tag}\t[进入等待队列]");
            if (Monitor.Wait(locker))
                ConsoleWriteLine($"{tag}\t[重新获得锁]");
            else
                ConsoleWriteLine($"{tag}\t[重新获得锁(超时)]");

            SpinWait.SpinUntil(() => start >= 7);

            ConsoleWriteLine($"{tag}\t[退出]");
            Monitor.Exit(locker);
        }
        public static void C(string tag)
        {
            SpinWait.SpinUntil(() => start >= 1);
            Thread.Sleep(20);

            ConsoleWriteLine($"{tag}\t[进入就绪队列]");
            Monitor.Enter(locker);
            ConsoleWriteLine($"{tag}\t[获得锁]");

            SpinWait.SpinUntil(() => start >= 3);

            ConsoleWriteLine($"{tag}\t[PulseAll]");
            Monitor.PulseAll(locker);

            SpinWait.SpinUntil(() => start >= 4);

            ConsoleWriteLine($"{tag}\t[退出]");
            Monitor.Exit(locker);
        }
        public static void D(string tag)
        {
            SpinWait.SpinUntil(() => start >= 2);
            Thread.Sleep(40);

            ConsoleWriteLine($"{tag}\t[进入就绪队列]");
            Monitor.Enter(locker);
            ConsoleWriteLine($"{tag}\t[获得锁]");

            SpinWait.SpinUntil(() => start >= 5);

            ConsoleWriteLine($"{tag}\t[退出]");
            Monitor.Exit(locker);
        }
        public static void E(string tag)
        {
            SpinWait.SpinUntil(() => start >= 2);
            Thread.Sleep(60);

            ConsoleWriteLine($"{tag}\t[进入就绪队列]");
            Monitor.Enter(locker);
            ConsoleWriteLine($"{tag}\t[获得锁]");

            SpinWait.SpinUntil(() => start >= 8);

            ConsoleWriteLine($"{tag}\t[退出]");
            Monitor.Exit(locker);
        }
    }
}

测试结果: 

C# 多线程同步方法 之 Monitor.Wait 与 Monitor.Pulse_第3张图片

你可能感兴趣的:(C#,.NET,编程,c#,多线程)