专栏首页架构师进击谷歌:多线程下程序执行顺序怎么稳定不乱?

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

面试官您好,我是来面试的

您好,我是这次的面试官,先介绍一下自己把

我是女孩,blala ....

那问一个多线程的问题吧,在一个多线程的环境中,怎么能保证一系列方法的执行顺序呢?

01

PART

题目

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

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执行 第三个方法==

面试官大哥,我好像不会哦

没关系,留下你的联系方式,如果可以,我们会有后续人员联系你的,感谢你来面试

02

PART

题解分析

这道题本质上考的是多线程环境资源竞争的问题,在多线程环境下要想以固定某个顺序执行方法,必须要有一个类似屏障的东西来阻止不该执行的线程,或者换句话说必须要有一个机制来顺序化多个线程。这也是我们平时多线程开发环境中必须要面对的问题,现代服务器的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资源的情况,可以替换为自旋锁或者混合锁,有兴趣的同学可以进行优化一番。

03

PART

公司简介

但是,作为语法很优秀的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方式提高的吞吐量,并非缩短了单次请求的执行时间。

助你早日进入谷歌!!!

本文分享自微信公众号 - 架构师修行之路(jiagoushixiuxing),作者:菜v菜

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 比Redis还快5倍的中间件,为啥这么快?

    今天给大家介绍的是KeyDB,KeyDB项目是从redis fork出来的分支。众所周知redis是一个单线程的kv内存存储系统,而KeyDB在100%兼容re...

    架构师修行之路
  • C# 8中的Async Streams

    C# 5引入了Async/Await,用以提高用户界面响应能力和对Web资源的访问能力。换句话说,异步方法用于执行不阻塞线程并返回一个标量结果的异步操作。

    架构师修行之路
  • c#基础系列3---深入理解ref 和out

    在上篇文章深入理解值类型和引用类型的时候,有的小伙伴就推荐说一说ref和out 关键字,昨天晚上彻夜难眠在想是否要谈一下呢,因为可谈的不是太多,也可能是我理解的...

    架构师修行之路
  • 最全面试宝典-我的春招总结

    今年上半年经过接近俩个月的紧张面试,终于拿到了满意的offer。春招竞争之难,超乎我的想象。所以劝告各位学弟学妹,一定要把握秋招。俩个月里,基本上面的都是一些大...

    用户7557625
  • JAVASE中的多线程小结,多生产多消费案例.

    因为线程任务已经被封装到Runnable接口中的run方法中,而这个run方法属于Runable接口的子类对象,所以要将这个子类对象作为参数传递给Thread类...

    帅的一麻皮
  • 面试必备之深入理解自旋锁

    分享一个我自己总结的Java学习的系统知识点以及面试问题,目前已经开源,会一直完善下去,欢迎建议和指导欢迎Star: https://github.com/Sn...

    用户2164320
  • 2020最新Java面试题,常见面试题及答案汇总

    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~

    用户7886150
  • How product extension field is involved in search scenario

    Jerry Wang
  • 【漏洞细节】VTech平板电脑:多个数据泄露的隐患暗藏

    在最近的VTech数据泄露事件后,大家或许认为VTech的电子玩具安全性不会比现在更糟糕了。然而就在这种情况下,我们又发现了两个漏洞,黑客可以从孩子们的Inno...

    FB客服
  • Golang语言-写一个检测 tcp udp 状态的包

    因为要跟着 dockerpool 的基友写 Docker 管理的程序,所以今年的目标是学习 go 语言。 看完 go 的入门教程,高深的代码还看不懂,所以决定先...

    李海彬

扫码关注云+社区

领取腾讯云代金券