• 进击谷歌:多线程下程序顺序怎么稳定不乱?


    题目

    比如我们有三个方法,类似以下代码

     static void Methond1()
            {
                Console.WriteLine($"{Thread.CurrentThread.Name} 执行 第一个方法==");
            }
            static void Methond2()
            {
                Console.WriteLine($"{Thread.CurrentThread.Name} 执行 第二个方法=======");
            }
            static void Methond3()
            {
               Console.WriteLine($"{Thread.CurrentThread.Name} 执行  第三个方法=======");
            }
        }
    
    

    分别初始化三个线程去执行这三个方法,每个线程执行的方法可以任意指定,但是方法的执行顺序必须是:Methond1=》Methond2=》Methond3

    示例:

    输入:3,2,1  
    第一个线程执行方法3,第二个线程执行方法2,第三个线程执行方法1
    输出:Methond1的输出=》Methond2的输出=》Methond3 的输出 ,即:
    xxx执行 第一个方法==
    xxx执行 第二个方法==
    xxx执行 第三个方法==
    
    

    题解分析

    这道题本质上考的是多线程环境资源竞争的问题,在多线程环境下要想以固定某个顺序执行方法,必须要有一个类似屏障的东西来阻止不该执行的线程,或者换句话说必须要有一个机制来顺序化多个线程。这也是我们平时多线程开发环境中必须要面对的问题,现代服务器的cpu都是多核心,我们平时用的最多的java,c#等高级语言是能够利用服务器多核心这个优势的,这里是一个进程内,多线程就可以利用多核心。当然也有一些别的技术,比如Nodejs,虽然执行是单线程的,但是可以利用多进程来提高多物理cpu的利用率。

    说的有点远了,接下来还是看这道题,这道题目考验的是多线程下资源的竞争问题和线程间的通信问题,说的直白一点就是:Methond1未执行之前,任何方法都不能执行(这里是指方法内的代码),Methond1执行完之后,会通知执行Methond2的线程,Methond2开始执行,Methond2执行完毕会通知执行Methond3的线程,以此类推。所以这道题的关键在于阻塞某个线程执行和通知某个线程执行。

    在很多语言中都提供了lock的机制,或者信号量的机制。这些都是可以实现线程间通信的的解决方案,当然最简单暴力的方式还是通过共享变量的方式来实现通知,虽然有很多缺陷,但是在很多情况下确实是最简单快捷的方案。下边就以c#语言为例:

     class Program
        {
            //把三个方法分别对应到 委托,为了方便执行方法
            static Action FirstAction = Methond1;
            static Action SecondAction = Methond2;
            static Action ThirdAction = Methond3;
    
            static Dictionary<int, Action> ActionDic = new Dictionary<int, Action>() { { 1, FirstAction }, { 2, SecondAction }, { 3, ThirdAction } };
            static void Main(string[] args)
            {
                while (true)
                {
                    Console.WriteLine("请输入顺序");
                    var inputStr = Console.ReadLine();
                    IsFinish1 = IsFinish2 = false;
    
                    var input = inputStr.Split(",").Select(s => int.Parse(s));
                    foreach (var i in input)
                    {
                        var method = ActionDic.GetValueOrDefault(i);
                        Thread t = new Thread(method.Invoke);
                        t.Name = "线程" + i;
    
                        Console.WriteLine($"{t.Name} 执行方法 {method.Method.Name}");
                        t.Start();
                    }
                }
            }
    
            static object objLock = new object();
            static bool IsFinish1 = false; //第一方法是否执行完毕
            static bool IsFinish2 = false; //第二方法是否执行完毕
    
    
            static void Methond1()
            {
                Console.WriteLine($"{Thread.CurrentThread.Name} 执行 第一个方法==");
                IsFinish1 = true;
    
            }
            static void Methond2()
            {
                while (true)
                {
                    if (IsFinish1)
                    {
                        Console.WriteLine($"{Thread.CurrentThread.Name} 执行 第二个方法=======");
                        IsFinish2 = true;
                        break;
                    }
                }
            }
            static void Methond3()
            {
                while (true)
                {
                    if (IsFinish2)
                    {
                        Console.WriteLine($"{Thread.CurrentThread.Name} 执行  第三个方法=======");
                        break;
                    }
                }
            }
        }
    

    测试执行:

    请输入顺序
    2,3,1
    线程2 执行方法 Methond2
    线程3 执行方法 Methond3
    线程1 执行方法 Methond1
    请输入顺序
    线程1 执行 第一个方法==
    线程2 执行 第二个方法=======
    线程3 执行  第三个方法=======
    3,2,1
    线程3 执行方法 Methond3
    线程2 执行方法 Methond2
    线程1 执行方法 Methond1
    请输入顺序
    线程1 执行 第一个方法==
    线程2 执行 第二个方法=======
    线程3 执行  第三个方法=======
    

    不要太纠结以上的代码,以上只是作为抛砖引玉的作用,就算是有执行错误,思路我觉得还是没有问题的,当然确实有很多要优化的地方,比如等待执行的线程一直在耗费cpu资源的情况,可以替换为自旋锁或者混合锁,有兴趣的同学可以进行优化一番。

    另辟蹊径

    但是,作为语法很优秀的c#怎么能就此止步呢?c#本身提供了Task的机制,可以把它看做是类似golang的协程(虽然不是很像)。基于task的机制我们很容易就完成,task的 ContinueWith机制就是为此而生的,它可以很方便的把多个并行的task串行化,有兴趣的同学可以试一下,但是我要说的并非ContinueWith。既然要执行的顺序化,如果我把执行的代码放在有顺序的容器中,然后按照顺序去执行,岂不快哉?不多废话:

    class TaskTest
        {
            static Func<Task> FirstAction = Methond1;
            static Func<Task> SecondAction = Methond2;
            static Func<Task> ThirdAction = Methond3;
            static Dictionary<int, Func<Task>> ActionDic = new Dictionary<int, Func<Task>>() { { 1, FirstAction }, { 2, SecondAction }, { 3, ThirdAction } };
            static Func<Task>[] tList = new Func<Task>[3];
           public static void Start()
            {
                while (true)
                {
                    Console.WriteLine("请输入顺序");
                    var inputStr = Console.ReadLine();
                 
                    var input = inputStr.Split(",").Select(s => int.Parse(s));
                    foreach (var i in input)
                    {
                        var method = ActionDic.GetValueOrDefault(i);
                        tList[i-1] = method;
                    }
                    foreach (var t in tList)
                    {
                        t.Invoke().Wait();
                    }
                }
            }
    
            static Task Methond1()
            {
               return Task.Run(()=> {
                    Console.WriteLine($"{Thread.CurrentThread.Name} 执行 第一个方法==");
                });
            }
            static Task Methond2()
            {
               return  Task.Run(() => {
                    Console.WriteLine($"{Thread.CurrentThread.Name} 执行 第二个方法=======");
                });
            }
            static Task Methond3()
            {
                return Task.Run(() => {
                    Console.WriteLine($"{Thread.CurrentThread.Name} 执行  第三个方法=======");
                });
            }
        }
    

    测试:

    请输入顺序
    2,3,1
     执行 第一个方法==
     执行 第二个方法=======
     执行  第三个方法=======
    请输入顺序
    3,2,1
     执行 第一个方法==
     执行 第二个方法=======
     执行  第三个方法=======
    请输入顺序
    1,3,2
     执行 第一个方法==
     执行 第二个方法=======
     执行  第三个方法=======
    请输入顺序
    

    是不是很爽,毫不犹豫的说c#是一门很优秀的语言,在并发方面做的很优秀。以上Task的代码并非是最优代码,比如 可以用Await来代替 wait(),这样的话在真实环境中可以释放当前线程来提高吞吐量,记住:await方式提高的吞吐量,并非缩短了单次请求的执行时间。

  • 相关阅读:
    使用批处理发布 QT 的程序
    转载:单一小农经济结构是我国长期动乱贫穷的病根
    JavaScript 盖尔-沙普利算法
    c# 自定义控件之 ComboBox
    c# PID算法入门
    c# 盖尔-沙普利算法的改进
    c# 陈景润 15 子问题
    c# 排序算法可视化
    c# 线程,同步,锁
    c# 小票打印
  • 原文地址:https://www.cnblogs.com/zhanlang/p/12777168.html
Copyright © 2020-2023  润新知