.NETCore 多线程学习笔记(多线程、线程结束顺序掌控、线程相对平均分配) 您所在的位置:网站首页 多线程的执行顺序 .NETCore 多线程学习笔记(多线程、线程结束顺序掌控、线程相对平均分配)

.NETCore 多线程学习笔记(多线程、线程结束顺序掌控、线程相对平均分配)

2023-01-22 03:52| 来源: 网络整理| 查看: 265

参考资料:多线程MutiThread最佳实践专题-1-1_哔哩哔哩_bilibili

跟着视频学习的,下面是自己的注释笔记和实验结果

 写了个窗体来学习线程的 多线程、线程掌控、线程分配

下面会用到这个方法

        ///         /// 仅仅只是一个耗时方法         ///         ///         private void DoSomethingLong(string name)         {             Debug.WriteLine($"*****DoSomethingLong start  {name} {Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToString("hh:mm:ss:fff")}*****");             long lResult = 0;             for (int i = 0; i < 1_000_000_000; i++)             {                 lResult += i;             }             Debug.WriteLine($"*****DoSomethingLong end    {name} {Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToString("hh:mm:ss:fff")} {lResult}*****");         }

单线程

只是个简单的例子,用于与下面多线程来做对比和参考

        ///         /// 单线程         ///         ///         ///         private void button1_Click(object sender, EventArgs e)         {             Debug.WriteLine(" ");             Debug.WriteLine($"*****button1_Click 同步方法 start {Thread.CurrentThread.ManagedThreadId}*****");  //当前线程的id,任意操作的相应都需要线程,方便开发的时候对线程的分析

            for (int i = 0; i < 5; i++)             {                 string name = $"button1_Click_{i}";                 //DoSomethingLong(name);                 Action action = () => DoSomethingLong(name);  //这是一个委托                 action.Invoke();//这仍然是单线程的,用于举例学习             }

            Debug.WriteLine($"*****button1_Click 同步方法 end {Thread.CurrentThread.ManagedThreadId}*****");             Debug.WriteLine(" ");         }

输出:

 可以看到是按照顺序输出了五遍,并且相隔一两秒。

多线程

        ///         /// 多线程         /// 一、.NET Framework1.0 2.0 3.0 3.5 4.0 4.5 4.8 -->.NET Core 1.0 2.0 2.2 3.0 3.1 -->.NET5-->.NET6         /// 经过了上面这些.net的一些大版本发展史,有很多的异步多线程的写法         /// 例:Thread/ThreadPool/BeginInvoke(netcore中已移除)/Task/Parallel/AsyncAwait         /// 公认最佳实践:Task         /// 二、十个线程是单线程的10倍速度吗?why?         /// 不到十倍,两种情况:1、CPU不行 2、调度机制、线程切换,如10个线程,cpu分片,线程调度,上下文切换都是需要成本的         /// 注意:线程不是越多越好。太多可能挂掉,但是Task不会出现,因为他是基于线程池的,但是太多了也会降低性能。         /// 那多少合适呢?控制在:开发的时候控制CPU核数*3 个线程以内(网络的经验之谈。扩展:线程池默认是2048个线程,如果你要Task开2048个是开不了的,但是Thread是可以的,那自然就挂了)         /// 多线程虽快,但请勿贪杯         /// 三、多线程是无序的,因为线程是计算机资源(不像c#可控)。启动时无序的,因为CPU调度策略,结束也是无序的。         /// 如果需求要求必须是有序的。 使用线程sleep就可以了吗?答案:不一定,因为程序执行的时间超出睡眠时间就出现问题,所以不可取。         /// 有多线程有序的解决方案吗?有!在button3_Click         ///         ///         ///         private void button2_Click(object sender, EventArgs e)         {             Debug.WriteLine(" ");             Debug.WriteLine($"*****button1_Click 同步方法 start {Thread.CurrentThread.ManagedThreadId}*****");  //当前线程的id,任意操作的相应都需要线程,方便开发的时候对线程的分析

            for (int i = 0; i < 10; i++)             {                 string name = $"button1_Click_{i}";//此时会阻塞,产生新的name                 Action action = () => DoSomethingLong(name);  //这是一个委托,如有返回值使用:Func

                #region 常见错误                 //注意!下面这句代码执行会出现i重复或乱掉的问题,这是一个执行时机和上下文的问题:                 //前者正确但是这句代码不正确,是因为循环时i没有阻塞(或者说循环内i是公共的),动作是交给Task线程去执行的(Task.Run并非立即执行,他只是交给线程的一个通知)。                 //Task线程是什么时候启动的?这个是不确定的,因为电脑系统等着执行的时候,此时i已经不是之前的i了,因为i可能已经循环到8或者10了,所以会有i重复或乱掉的出现                 //前者可以,是因为在循环时把i绑定到name变量上(每次循环都是新的name,此时阻塞了一下)                 //这个被称为:多线程的临时变量问题                 //Action action1 = () => DoSomethingLong($"button1_Click_{i}");                 #endregion

                Task.Run(action);//开启多线程!(循环5次就是5个线程并发执行)             }

            Debug.WriteLine($"*****button1_Click 同步方法 end {Thread.CurrentThread.ManagedThreadId}*****");             Debug.WriteLine(" ");         }

输出:

 可以看到五个线程是同时执行的,到最后结束的时间都基本相同

线程结束顺序的掌控

        ///         /// 线程结束顺序的掌控(线程的 阻塞&非阻塞)         ///         ///         ///         private void button3_Click(object sender, EventArgs e)         {             Debug.WriteLine($"*****button3_Click  start {Thread.CurrentThread.ManagedThreadId}*****");             List taskList = new List();             taskList.Add(Task.Run(() => { DoSomethingLong("开发1"); }));             taskList.Add(Task.Run(() => { DoSomethingLong("开发2"); }));             taskList.Add(Task.Run(() => { DoSomethingLong("开发3"); }));             taskList.Add(Task.Run(() => { DoSomethingLong("开发4"); }));             taskList.Add(Task.Run(() => { DoSomethingLong("开发5"); }));

            #region 普通写法,当然这样是阻塞主线程的(winform窗口是不能拖动的)             //Task.WaitAny(taskList.ToArray()); //阻塞到任意一个任务完成(应用场景如:多渠道获取数据,redis、es、本地数据库 等等)             //Debug.WriteLine("任意一个人完成了任务!!!!");

            //Task.WaitAll(taskList.ToArray());//阻塞到全部task完成。但是winform界面会卡主,因为主线程在等待。             //Debug.WriteLine("全部人完成任务了!!!!");             #endregion

            #region 解决阻塞问题

            #region 方法1:在最外层包一层线程。可以实现,浪费服务器资源,千万别乱包(3层以后,神仙难救,经验之谈,不推荐)             Task.Run(() =>             {                Task.WaitAny(taskList.ToArray()); //阻塞到任意一个任务完成(应用场景如:多渠道获取数据,redis、es、本地数据库 等等)                Debug.WriteLine("任意一个人完成了任务!!!!");

               Task.WaitAll(taskList.ToArray());//阻塞到全部task完成。                Debug.WriteLine("全部人完成任务了!!!!");             });             #endregion

            #region 方法2:使用Task和Task.Factory提供的接口。(推荐)             //1、指定某个任务完成后,做非阻塞回调             taskList[0].ContinueWith(x => { Debug.WriteLine("非阻塞,开发1完成了任务!!!!"); });             //2、指定任意一个任务完成后,做非阻塞回调             Task.Factory.ContinueWhenAny(taskList.ToArray(), x => { Debug.WriteLine("非阻塞,任意一个人最先完成了任务!!!!"); });             //3、全部任务完成后,做非阻塞回调             Task.Factory.ContinueWhenAll(taskList.ToArray(), x => { Debug.WriteLine("非阻塞,全部人完成任务了!!!!"); });

            #region 扩展(阻塞与非阻塞灵活搭配)             //taskList[0].ContinueWith(x => { Debug.WriteLine("非阻塞,开发1完成了任务!!!!"); });             //taskList.Add(Task.Factory.ContinueWhenAny(taskList.ToArray(), x => { Debug.WriteLine("非阻塞,任意一个人最先完成了任务!!!!"); }));             //taskList.Add(Task.Factory.ContinueWhenAll(taskList.ToArray(), x => { Debug.WriteLine("非阻塞,全部人完成任务了!!!!"); }));

            此时上面的非阻塞可能会比下面的阻塞(WaitAll)还晚执行完,那不就有问题了吗,如果想让上面的非阻塞线程也加到下面的阻塞中怎么做?             答:可以把上面的非阻塞线程 加到任务list中。如:taskList.Add(Task.Factory.ContinueWhenAll()),这样的话WaitAll就可以完全阻塞所有线程了。             //Task.WaitAny(taskList.ToArray());             //Debug.WriteLine("阻塞,任意一个人最先完成了任务(包含非阻塞)");             //Task.WaitAll(taskList.ToArray());//阻塞线程,因为上面的非阻塞线程taskList.Add到了任务列表中,所以这里阻塞本身的 "阻塞线程" 和 "非阻塞线程"。             //Debug.WriteLine("阻塞,全部人完成任务了(包含非阻塞)");             #endregion

            #endregion

            #endregion

            Debug.WriteLine($"*****button3_Click  end   {Thread.CurrentThread.ManagedThreadId}*****");  //         }

输出:

 很清晰明了,可以知道:某一个指定任务啥时候完成、第一个完成的任务、所有任务完成后,再做相对应的回调方法。然后一些注释的地方也做了一个阻塞与非阻塞的例子搭配使用。这些方法可以基本实现90%的业务场景,灵活应用即可。

可能有人问:这是获得了任务的回调方法,怎么掌控执行顺序?那你可以想一下,既然你要控制他的执行顺序的话,那为啥还要用多线程,挨个调用不得了。。。没太大意义

多线程相对平均分配

        ///         /// 多线程分配         ///         ///         ///         private void button4_Click(object sender, EventArgs e)         {             ShowTaskDispatcher();         }

        ///         /// 有100个任务并行执行,每个任务的量是不一样的可能是1w可能是10w...         /// 给27个线程,怎么分配         /// 如果不知道任务数量怎么办?更没有规律,给27个线程,如何近似均匀的分配任务?         ///          ///         private void ShowTaskDispatcher()         {             #region 初始化数据(模拟每个任务的量不一样,数组累加1到100)             int[][] typeaArray = new int[100][];             for (int i = 0; i < 100; i++)             {                 int[] innerArray = new int[i];                 for (int j = 0; j < i; j++)                 {                     innerArray[j] = j;                 }                 typeaArray[i] = innerArray;             }             #endregion

            List taskList = new List();

            int threadNum = 27;

            #region 方法1,手写代码分配。

            //#region 线程任务划分             //List dispacherTypeList = new List();             //for (int i = 0; i < threadNum; i++)             //{             //    dispacherTypeList.Add(new List() { });//初始化27个空集合的数组             //}             //for (int i = 0; i < 100; i++)             //{             //    dispacherTypeList[i % threadNum].Add(typeaArray[i]); //100个任务,分组平均分配             //}

            //#endregion

            //for (int i = 0; i < threadNum; i++)             //{             //    List currentTypeArray = dispacherTypeList[i];//分配好的线程的任务,注:此处一定得弄临时变量。

            //    taskList.Add(Task.Run(() =>             //    {             //        foreach (var array in currentTypeArray)             //        {             //            Debug.WriteLine($"CurrentThreadId={Thread.CurrentThread.ManagedThreadId},执行{string.Join(",", array)}");             //            foreach (var num in array)             //            {             //                Thread.Sleep(num);             //            }             //        }

            //        Debug.WriteLine($"CurrentThreadId={Thread.CurrentThread.ManagedThreadId},任务完成");             //    }));

            //}             #endregion

            #region 方法2,利用Task.WaitAny来判断实现,好像没方法3快             //for (int i = 0; i < typeaArray.Length; i++)             //{             //    var currentTypeArray = typeaArray[i];             //    taskList.Add(Task.Run(() =>             //    {             //        Debug.WriteLine($"CurrentThreadId={Thread.CurrentThread.ManagedThreadId},执行{string.Join(",", currentTypeArray)}");             //        foreach (var num in currentTypeArray)             //        {             //            Thread.Sleep(num);             //        }

            //        Debug.WriteLine($"CurrentThreadId={Thread.CurrentThread.ManagedThreadId},任务完成");             //    }));

            //    if (taskList.Count == threadNum)//到了最大线程数             //    {             //        Task.WaitAny(taskList.ToArray());//等待,只要有一个线程提前完成,说明有线程空闲             //        taskList = taskList.Where(x => x.Status == TaskStatus.Running).ToList();//获取所有运行中的线程更新给集合(当然,不包含waitany完成的那个)             //    }             //}             #endregion

            #region 方法3,这个更快速             ParallelOptions options = new ParallelOptions()             {                 MaxDegreeOfParallelism = threadNum//最大线程数             };             Parallel.ForEach(typeaArray, options, currentTypeArray =>             {                 Debug.WriteLine($"CurrentThreadId={Thread.CurrentThread.ManagedThreadId},执行{string.Join(",", currentTypeArray)}");                 foreach (var num in currentTypeArray)                 {                     Thread.Sleep(num);                 }

                Debug.WriteLine($"CurrentThreadId={Thread.CurrentThread.ManagedThreadId},任务完成");             });             #endregion

            Task.WaitAll(taskList.ToArray());//         }

不展示输出了,因为方法3:Parallel.ForEach就是自动分配的,一开始配置了MaxDegreeOfParallelism=27 所以他会均匀的分配并执行任务。当然方法2也是可用的方法

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有