C#_多线程

什么是应用程序域?

应用程序域可以看成是程序集的“容器”,应用程序域可以被主动创建,可以被卸载,并且很快被GC(垃圾回收器)回收。

在.net平台托管代码运行时,CLR(公共语言运行库)还会额外的创建“系统域”和“共享域”,存放应用程序需要的资源,这样就能减少 进程的总数,提高系统性能,减轻进度调度的压力;

        #region 应用程序域
        private void button8_Click(object sender, EventArgs e)
        {
            int countq = System.Diagnostics.Process.GetProcesses().Length;
            Console.WriteLine("【加载应用程序域之前】进程总数"+countq);

            //创建应用程序域
            var newDomain = AppDomain.CreateDomain("newAppDomain");

            //加载程序集
            newDomain.ExecuteAssembly("D:\\NZP\\NZP_WindowsForms\\NZP_WindowsForms\\bin\\Debug\\NZP_WindowsForms.exe");
            Console.WriteLine("【加载应用程序域之后】进程总数" + countq);

            //卸载应用程序域
            AppDomain.Unload(newAppDomain);


            //效果:
            //打开两个应用,只开启一个进程。
        }
        #endregion

线程状态

  • 挂起(Suspend):

  • 亦称“暂停”。挂起是用户主动发起的行为,所以可以恢复;线程被挂起的时候,CPU资源不被释放;如果任务优先级高,其他任务则靠边站。

  • 阻塞(Blocked):

  • 是一种被动的行为,但是任务会释放CPU,其他任务可以运行;阻塞一般是在等待某些资源或信号量的时候出现,所以不确定什么时候恢复。

  • 休眠(Sleep):

  • 方法没有释放锁,线程调用的时候,CPU资源一直占有(占着茅坑不拉屎)
  • 等待(Wait):

  • 方法会释放锁,其他线程可以使用资源。

Sleep()和Wait()都可以让程序等待多少毫秒,如:

sleep(2000)表示:占用CPU。程序休眠2秒。

Wait(2000)表示:不占用CPU。程序等待2秒。

线程开销

线程在内存空间上的的开销

【1】Thread=内核数据:将基于时间片把当前线程中的资源随时保存至Contex(存放CPU寄存器相关的变量),然后线程休眠,再次开启其他线程的调度。

【2】Thread环境块:

  • 主线程
  • 终结器(也就是GC用来回收资源)
  • 应用程序线程

【3】用户堆栈模式:用户程序的“局部变量”和“参数传递”所使用的堆栈。

经常见到StackOverFlowException,内存溢出的基本原因其实就是“堆栈溢出”。

【4】内核堆栈模式:在CLR(公共语言运行库)的线程操作的时候,通常最后会调用操作系统(底层win32函数),用户模式中的参数需要传递到内核模式中

线程在时间上的开销

【1】资源使用通知的开销

我们在运行一个程序,通常会加载很多的托管的、非托管DLL、exe、资源、元数据

【2】时间片切换开销

程序的线程数,会远远超过系统的线程数量,即使很多线程在休眠状态;在这样情况下,时间片需要在不同的线程中频繁切换,从而导致时间耗费。

单线程示例

        #region ① 单线程做菜
        /// <summary>
        /// ① 单线程做菜:执行任务时,什么操作都动不了.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SingleThread_Click(object sender, EventArgs e)
        {
            Thread.Sleep(3000);
            MessageBox.Show("素菜做好了","友情提示");
            Thread.Sleep(3000);
            MessageBox.Show("荤菜做好了", "友情提示");
        }
        #endregion

单线程_效果
做菜(先做完素菜,再做荤菜)时,当前窗口无法操作(移动、拉伸等)。

多线程概述

字面理解:多个线程同时工作的过程

句柄:可以理解为程序里面的所有对象资源的一个别名,类似于指针;比如一个窗口它就是一个句柄,窗体里面的控件也是一个句柄。

为什么要用多线程?

1 提升CPU利用率:每个CPU在每个时间片内只能执行一个线程,而多线程技术可以使多个任务并发执行,充分利用系统资源。

2 改善程序架构:复杂程序可以分解多个线程,而这些线程可以独立或半独立地运行,使得程序更易于理解和修改。

3 提高应用程序响应:对于图形界面的程序,可以将耗时的操作放在后台线程中处理,程序界面仍然可以保持响应

切记:线程越多,上下文切换的开销也越大;线程间同步容易出错,且不易于调试。

多线程如何实现?

1 基于委托异步实现

  • 异步编程是建立在委托的基础上实现的多线程方法。
  • 异步调用的每个方法都是独立的线程中进行的,因此异步编程就是多线程一种,可以说是一种简化的多线程。
  • 比较适合在后台耗费时间较长,但是任务简单的情况,并且任务之间是独立的。
  •  如果后台要求访问共享资源,并且要按照某些顺序执行,则异步不适合;应该采用多线程去完成。
        private static Random random = new Random();//随机数实例

        //定义一个任务,基于委托实现求一个数的平方
        private Func<int, int> Operation = (num) =>
        {
            Thread.Sleep(random.Next(5) * 1000);
            return num * num;
        };

        /// <summary>
        从这里开始看程序------同时执行多个任务
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button3_Click(object sender, EventArgs e)
        {
            for (int i = 1; i < 11; i++)
            {
                //Operation(1);//同步调用

                //异步调用
                //i * 10:委托输入参数
                //(result):表示回调函数AsyncCallBack;就是异步执行完后,自动调用的方法。
                //i :给回调函数字段AsyncState赋值,如果参数很多则将参数定义成结构或者对象
                //返回值:IAsyncResult-异步操作状态接口,封装了异步中执行的参数
                Operation.BeginInvoke(i*10,AutoCallBack,i);
                //简写版
                //Operation.BeginInvoke(i * 10,
                //(result) =>
                //{
                //    //EndInvoke 通过IAsyncResult接口对象,在任务执行中,不断的去监测异步调用是否结束
                //    Console.WriteLine($"第{result.AsyncState}个计算结果:{Operation.EndInvoke(result)}");
                //}, i);

            }
        }

        /// <summary>
        /// 回调函数
        /// </summary>
        /// <param name="result"></param>
        private void AutoCallBack(IAsyncResult result)
        {
            //EndInvoke 通过IAsyncResult接口对象,在任务执行中,不断的去监测异步调用是否结束
            Console.WriteLine($"第{result.AsyncState}个计算结果:{Operation.EndInvoke(result)}");
        }


        显示结果:
        //第2个计算结果:400
        //第10个计算结果:10000
        //第3个计算结果:900
        //第5个计算结果:2500
        //第8个计算结果:6400
        //第4个计算结果:1600
        //第6个计算结果:3600
        //第7个计算结果:4900
        //第1个计算结果:100
        //第9个计算结果:8100

2 Thread

只要我们需要异步任务,都会开启一个Thread,执行完后不会立即被GC回收,而是从Thread.InternalFinalize()进入终结器,按照CLR的机制进入终结器后面还有可能被激活,如果没有被激活则下次会被GC回收,所以必然存在时间和空间开销。

Thread类:每一个Thread对象都代表这是一个托管线程,每个托管线程都对应这一个函数,属于“逻辑线程”。

ProcessThread类:用于操作系统中真实的本地线程。

关于前台线程和后台线程:

  • 一个程序如果有前台线程,必须在所有的前台线程都结束后,才能退出。
  • 一个程序如果开启的都是后天线程,则程序关闭的时候,后台线程也就自动全部退出。
        #region ② Thread做菜
        /// <summary>
        /// ② 多线程做菜:执行任务时,可以对其他功能操作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void multiThreading_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                Thread.Sleep(3000);
                MessageBox.Show("素菜做好了", "友情提示");
                Thread.Sleep(5000);
                MessageBox.Show("荤菜做好了", "友情提示");
            });
            thread.IsBackground = true ; //设置为后台线程,(通常要这样设置)
            thread.Start();
        }
        #endregion

Thread_效果:

做菜(先做完素菜,再做荤菜)时,当前窗口进可以行操作(移动、拉伸等)。
Thread实例方法_开启、暂停、恢复、终止、中断、等待
        #region Thread版本的线程生命周期控制

        private Thread childThread;
        private int i;
        /// <summary>
        /// 开启
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button9_Click(object sender, EventArgs e)
        {
            childThread = new Thread(() =>
            {
            try
            {
                for (i = 1; i <= 100; i++)
                {
                    Thread.Sleep(500);
                        progressBar1.Invoke(new Action(() =>
                        {
                            progressBar1.Value = i;//将异步任务进度百分比赋值给进度条
                        }));
                        label2.Invoke(new Action(() =>
                        {
                            label2.Text = (i.ToString() + "%");//将异步任务进度百分比赋值给标签组件显示进度数值
                        }));
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"错误信息:{ex.Message} 异常发生的数字位置:{i}");
                }

            });
            childThread.Start();
        }

        /// <summary>
        /// 暂停  亦称“挂起”
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button10_Click(object sender, EventArgs e)
        {
            //正在运行的线程或者休眠的线程,都可以暂停;已经暂停的,不用暂停了。
            if (childThread.ThreadState == ThreadState.Running || childThread.ThreadState == ThreadState.WaitSleepJoin)
            {
                childThread.Suspend();
            }
        }

        /// <summary>
        /// 继续 即在暂停状态下进行恢复
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button11_Click(object sender, EventArgs e)
        {
            if (childThread.ThreadState == ThreadState.Suspended)
            {
                childThread.Resume();
            }
        }

        /// <summary>
        /// 终止 亦称“取消”
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button12_Click(object sender, EventArgs e)
        {
            childThread.Abort();
        }

        /// <summary>
        /// 中断 会抛出异常,提示线程中断
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button13_Click(object sender, EventArgs e)
        {
            childThread.Interrupt();
        }

        /// <summary>
        /// Join  会等待子线程执行完后,再执行下面的主线程的内容
        ///       不使用则是主线程不会等待子线程执行
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button14_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                Thread.Sleep(5000);
                Console.WriteLine("当前线程是子线程。。。。。。");
            });
            thread.Start();

            thread.Join();
            Console.WriteLine("当前线程是主线程。。。。。。");


            //当前线程是子线程。。。。。。
            // 线程 '[线程已销毁]'(14124) 已退出,返回值为 0(0x0)。
            //当前线程是主线程。。。。。。
        }

        #endregion
Thread静态方法_观察Debug和Release两种情况性能差别
运行时间差别
        private void button17_Click(object sender, EventArgs e)
        {
            List<long> dataList = new List<long>();
            Stopwatch stopwatch = new Stopwatch();//监测运行时间对象

            Random random = new Random();

            //随机生成10000个10000以内的数据
            for (int i = 0; i < 10000; i++)
            {
                long num=  random.Next(10000);
                dataList.Add(num);  
                Console.Write(num+"  ");             
            }
            Console.WriteLine("  ");

            Thread.Sleep(1000);

            //10次冒泡排序
            for (int i = 0;i < 10; i++)
            {
                stopwatch.Start();
                List<long> data = BubbleSolt(dataList);
                stopwatch.Stop();

                Console.WriteLine($"第{i}次:\t{stopwatch.ElapsedMilliseconds}") ;
            }

        }

        /// <summary>
        /// 冒泡排序
        /// </summary>
        /// <param name="dataList"></param>
        /// <returns></returns>
        static List<long> BubbleSolt(List<long> dataList)
        {
            long tempData;//临时数据

            for (int a = 0;a<dataList.Count-1;a++)
            {
                for (int b = dataList.Count-1;b>a;b--)
                {
                    if (dataList[b-1] > dataList[b])//如果前面一个数大于后面一个数则交换
                    {
                        tempData = dataList[b-1];
                        dataList[b-1] = dataList[b];
                        dataList[b] = tempData;
                    }
                }
            }
            return dataList;
        }

Debug效果:
第0次:	453
第1次:	599
第2次:	752
第3次:	904
第4次:	1055
第5次:	1208
第6次:	1362
第7次:	1516
第8次:	1664
第9次:	1817

Release效果:
第0次:	204
第1次:	268
第2次:	332
第3次:	397
第4次:	454
第5次:	515
第6次:	588
第7次:	646
第8次:	709
第9次:	776


结论:使用debug和release大概相差2-3倍,因为Release做了性能优化
赋值/取值差别
        private void button17_Click(object sender, EventArgs e)
        {
            bool stop1 = false;//主线程定义的一个变量
            int stop2 = 0;//主线程定义的一个变量
            int a1 = 0;
            int a2 = 0;

            Thread thread = new Thread(() =>
            {

                //=============================方法1====================================
                while (!stop1)//在子线程中访问主线的变量
                {   
                    //在这个方法之前的内存写入都要及时从CPU的cache中更新到memory中,然后从memory中读取,而不是cpu cache
                    Thread.MemoryBarrier();//同步内存访问
                    a1++;
                }

                //=============================方法2====================================
                while (stop2==0)//在子线程中访问主线的变量
                {
                    Thread.VolatileRead(ref stop2);//在memory中获取最新值
                    a2++;                  
                }

            });
            thread.Start();
            thread.IsBackground = false;
            Thread.Sleep(1000);

            //stop1 = true;   方法1
            stop2 = 1; //方法2


            thread.Join();

            Console.WriteLine("主线程执行完毕-a1:"+ a1);
            Console.WriteLine("主线程执行完毕-a2:"+ a2);
        }

        //效果:
        //主线程执行完毕-a1:90702182
        //主线程
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值