进击谷歌:多线程下程序顺序怎么稳定不乱?
题目
比如我们有三个方法,类似以下代码
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方式提高的吞吐量,并非缩短了单次请求的执行时间。
更多精彩文章

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
限流10万QPS、跨域、过滤器、令牌桶算法-网关Gateway内容都在这儿
一、微服务网关Spring Cloud Gateway 1.1 导引 文中内容包含:微服务网关限流10万QPS、跨域、过滤器、令牌桶算法。 在构建微服务系统中,必不可少的技术就是网关了,从早期的Zuul,到现在的Spring Cloud Gateway,网关我们用的不可少。 今天我就将沉淀下来的所有与网关相关的知识,用一篇文章总结清楚,希望对爱学习的小伙伴们有所帮助。 本篇文章主要介绍网关跨域配置,网关过滤器编写,网关的令牌桶算法限流【每秒10万QPS】 首先我们来看什么是网关 1.2 什么是微服务网关Gateway? This project provides a library for building an API Gateway on top of Spring WebFlux. gateway官网: https://spring.io/projects/spring-cloud-gateway 实现微服务网关的技术有很多, nginx Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务 zuul ,Zu...
- 下一篇
聊一聊 JAVA 泛型中的通配符 T,E,K,V,?
作者 |glmapper 出处 | https://juejin.im/post/6844903917835419661 前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 泛型带来的好处 在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。 那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。 publicclassGlmapperGeneric<T>{privateTt;publicvoidset(Tt){this.t=t;}publicTget(){returnt;}publicstaticvoidmain(String[]args){//...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2整合Redis,开启缓存,提高访问速度