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

news2024/12/27 11:25:39

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

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

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

下面会用到这个方法

        /// <summary>
        /// 仅仅只是一个耗时方法
        /// </summary>
        /// <param name="name"></param>
        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}*****");
        }

单线程

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

        /// <summary>
        /// 单线程
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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(" ");
        }

输出:

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

多线程

        /// <summary>
        /// 多线程
        /// 一、.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
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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<string>

                #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(" ");
        }

输出:

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

线程结束顺序的掌控

        /// <summary>
        /// 线程结束顺序的掌控(线程的 阻塞&非阻塞)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button3_Click(object sender, EventArgs e)
        {
            Debug.WriteLine($"*****button3_Click  start {Thread.CurrentThread.ManagedThreadId}*****");
            List<Task> taskList = new List<Task>();
            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%的业务场景,灵活应用即可。

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

多线程相对平均分配

        /// <summary>
        /// 多线程分配
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button4_Click(object sender, EventArgs e)
        {
            ShowTaskDispatcher();
        }

        /// <summary>
        /// 有100个任务并行执行,每个任务的量是不一样的可能是1w可能是10w...
        /// 给27个线程,怎么分配
        /// 如果不知道任务数量怎么办?更没有规律,给27个线程,如何近似均匀的分配任务?
        /// 
        /// </summary>
        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<Task> taskList = new List<Task>();

            int threadNum = 27;

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

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


            //#endregion

            //for (int i = 0; i < threadNum; i++)
            //{
            //    List<int[]> 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也是可用的方法

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/157578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32RTC外设详解

目录一.RTC 实时时钟简介1.RTC时钟来源2.RTC主要特性二.RTC 外设功能框图1.RTC功能框图剖析2.使能对后备寄存器和RTC的访问3.复位过程4.读RTC寄存器5.配置RTC寄存器三.实现一个简易时钟1.实验目的2.实验原理3.实验源码4.效果演示一.RTC 实时时钟简介 实时时钟是一个独立的定时…

链表题目总结 -- 双指针技巧

文章目录一. 合并两个有序链表1. 思路简述2. 代码3. 总结二. 分隔链表1. 思路简述2. 代码3. 总结三. 合并K个升序链表1. 思路简述2. 代码3. 总结四. 单链表的倒数第 k 个节点1. 思路简述2. 代码3. 总结五. 链表的中间结点1. 思路简述2. 代码3. 总结六. 环形链表&#xff08;链表…

docker一个容器内部署多个服务

原因是&#xff0c;我有一个springBoot服务需要写入httpd的目录&#xff0c;然后httpd提供链接给别人下载。之前的方法是&#xff0c;httpd和springBoot各一个容器&#xff0c;但是我们将镜像是部署在腾讯云上的&#xff0c;腾讯云会自动对每个容器分离不同的虚拟机&#xff0c…

pytorch简单自定义Datasets

前言 本文记录一下如何简单自定义pytorch中Datasets&#xff0c;官方教程文件层级目录如下&#xff1a; images 1.jpg2.jpg…9.jpg annotations_file.csv 数据说明 image文件夹中有需要训练的图片&#xff0c;annotations_file.csv中有2列&#xff0c;分别为image_id和labe…

Python的23种设计模式(完整版带源码实例)

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; Python的23种设计模式 一 什么是设计模式 设计模式是面对各种问题进行提炼和抽象而形成的解决方案。这些设计方案是前人不断试验&…

【入门篇】1 # 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?

说明 【数据结构与算法之美】专栏学习笔记。 什么是复杂度&#xff1f; 复杂度也叫渐进复杂度&#xff0c;包括时间复杂度和空间复杂度&#xff0c;用来分析算法执行效率与数据规模之间的增长关系&#xff0c;可以粗略地表示&#xff0c;越高阶复杂度的算法&#xff0c;执行…

时域脉冲通信采用高斯脉冲且使用PAM调制的Matlab简易演示仿真

时域脉冲通信采用高斯脉冲且使用PAM调制的Matlab简易演示仿真 环境 matlab 2016a 指标 1 将声音信号转为二进制码 2 PAM调制 3 采用高斯脉冲 流程 代码 [OriginVoice,fs]audioread(voice.m4a) ; OriginVoiceOriginVoice(:,2); Nlength(OriginVoice); % 计算信号x的长度 …

算法训练营 day15 二叉树 层序遍历 翻转二叉树 对称二叉树

算法训练营 day15 二叉树 层序遍历 翻转二叉树 对称二叉树 层序遍历 102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。…

标签设计打印软件:LabelJoy 6.23.0 Crack

LabelJoy 专业条码软件 生成25种条形码 从数据源导入条码 计算自动校验 商业条形码标签软件 兼容 Excel、Access、MySQL、Oracle 11.000 个预装的纸张布局 支持任何打印机 通过 3 个步骤创建和打印标签&#xff1a; 选择布局 创建您的标签 开始打印 最好的标签打印软件&#xf…

kafka-1

文章目录1.启动2.创建主题3.发送消息4.消费消息5.使用kafka connect将现有的数据导入到kafka中6.使用kafka streams处理kafka中的events6.终止服务集群配置要点创建主题要点主题分区变更主题副本可变更吗&#xff1f;创建生产者要点> tar -xzf kafka_2.12-3.3.1.tgz1.启动 …

Mac生成和查看ssh key

从 git 上拉取或者提交代码每次都需要输入账号密码&#xff0c;这样很麻烦。我们可以在电脑上生成一个 ssh key&#xff0c;然后把ssh key添加到 git 中&#xff0c;就可以不用每次去输账号密码了。下面就介绍一下怎么在自己的 Mac 中生成和查看 ssh key。一、Mac生成SSH Key打…

【环境】idea远程debug

工作中&#xff0c;遇到问题的时候&#xff0c;想知道上下文中对应的参数值是什么&#xff1f;这时候&#xff0c;1、我们可以全靠逻辑分析。费脑&#xff0c;不一定对。2、打印日志&#xff0c;打印的信息不一定全&#xff0c;换包&#xff0c;麻烦3、远程debug。 1、配置ide…

pytorch二维码识别

二维码图片的生成 利用captcha可以生成二维码图片 # -*- coding: UTF-8 -*- from captcha.image import ImageCaptcha # pip install captcha from PIL import Image import random import time import os # 验证码中的字符 # string.digits string.ascii_uppercase NUMBER…

整理了一周近万字讲解linux基础开发工具vim,gdb,gcc,yum等的使用

文章目录 前言一、yum的使用二、vim的使用三 . gcc/g的使用四 . gdb的使用总结前言 想用linux开发一些软件等必须要会的几种开发工具是必不可少的&#xff0c;在yum vim gcc gdb中指令繁杂的是vim和gdb这两个工具&#xff0c;至于yum和gcc的指令就比较简单了。 一、yum的使用…

【SpringMVC】拦截器

目录 一、概念 二、自定义拦截器的三个实现方法 三、自定义拦截器执行流程 四、使用 五、拦截器和过滤器 相关文章&#xff08;可以关注我的SpringMVC专栏&#xff09; SpingMVC专栏SpingMVC专栏一、概念在学习拦截器之前&#xff0c;我们得先了解一下它是个什么❓ SpringMVC…

SAP ABAP调用标准事务码

这里介绍常见的几种在开发中常用到的事务代码跳转功能。 1、最常用到的是“SET PARAMETER”语句赋值&#xff0c;然后再使用“CALL TRANSACTION”语句跳转屏幕。 比如采购订单、销售订单、交货单、采购发票、销售发票等事务代码&#xff0c;均可以利用给参数赋值来直接跳转&am…

零售及仓储数字化整理解决方案

价格管控 皮克价格管控方案可实现门店与企业信息管理平台的数据同步&#xff0c;强化零售企业对终端的控制。同时为企业销售决策提供支持&#xff0c;优化门店经营活动的效率和频率。陈列管理 皮克陈列管理方案通过电子价签产品使商品陈列得到固化。 同时实现了陈列可视化&am…

ArcGIS水文分析提取河网及流域

在进行某些研究或者一些论文插图显示的时候&#xff0c;有时我们会碰到在部分资料中找不到一些小的河流或者流域的数据的情况&#xff0c;这里讲述通过DEM数据生成河网及流域。 一、数据来源 四川省高程数据来源于中国科学院资源环境科学与数据中心&#xff08;中国科学院资源环…

Vue3学习之深度剖析CSS Modules和Scope

Css Modules 是通过对标签类名进行加装成一个独一无二的类名&#xff0c;比如.class 转换成.class_abc_123,类似于symbol&#xff0c;独一无二的键名 Css Scope 是通过为元素增加一个自定义属性&#xff0c;这个属性加上独一无二的编号&#xff0c;而实现作用域隔离。 原理 …

爬虫必备抓包工具——Fiddler【认识使用】

目录&#xff1a;1.fiddler &#xff08;抓包工具&#xff09;1.1 引入&#xff1a;HTTP/https代理&#xff08;正向代理&#xff09;1.2 拓展&#xff1a;反向代理&#xff1a;1.2 初识Fiddler①什么是抓包&#xff1f;抓包有什么用&#xff1f;②浅谈fiddler&#xff1a;③fi…