多线程 学习

1.为什么学多线程?

在单核时代,我们写的程序,如果没有多线程,就会经常卡顿,但是并不是说用了多线程就不会卡顿,因为在单核时代,即使你用了多线程,你也不是并行执行,其实是通过时间片的切换来达

到并行的目的。异步多线程在一定程度上可以提升系统的性能,改善用户的体验。

 

打开电脑的任务管理器,我们可以看到现在电脑的进程和线程数,一个进程被分为多个线程,我这个电脑是四核CPU,按理说我这电脑至少可以并行运行四个程序(一个程序理解为一个进程),

但是实际上电脑运行的远远不止四个程序,那它是怎么做到同时运行那么多任务的呢?可以看下面那张图,发现,大多数的进程CPU占用都为0,内存占用都很小,说明大多数的进程都处于休眠

状态,实际自己开启运行的并没有那么多程序。要理解这个,这里需要介绍一下时间片的概念,拿第三张图片,以30ms时间为一个时间单位,1s的时间是很快的,我们前60ms执行任务1,后面

90ms执行任务2.......,通过时间片的快速切换,实现假的并行状态。当你线程开了很多个,远远超过物理线程数的时候,会发现任务停止了,停止的原因就是时间片还没有切换过来,如果你的任

务非常耗时,电脑一下子就卡死了。所以开启多线程要“量力而行”,不是想开就开的,因为线程是会占用时间和空间的。

 

 

2.多线程底层观察

工具:windb软件   下载链接:https://pan.baidu.com/s/1gjR_ysN2AZh6VlCyawWcAw  提取码:be08 

目的:通过windb可以查看CLR级别的信息。

启动:首先要加载.NET调试扩展包SOS。

SOS命令都是以!开头,通过!help查询所有帮助

现在运行一个多线程程序,直接执行Debug文件下的可执行文件:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace D02_0_newApp
{
    class Program
    {
        static void Main(string[] args)
        {

            Thread thread = new Thread(() =>
            {
                Console.WriteLine("测试线程的资源占用情况!");
            });
            thread.IsBackground = true;
            thread.Start();

            Console.Read();
        }
    }
}

 

 

 

 

在上图中找到运行的程序,点击

先输入.loadby sos clr

再输入!threads

 看到 DeadThread :1

说明有一个线程已经执行完了

再输入:!ThreadState 39820     39820是XXXX的那个线程,一般就是主线程外的子线程。

 

 可以发现Dead,说明这个线程已经执行完了

 

3.线程在空间上的开销?

【1】Thead内核数据结构:线程的ID和线程环境都在CPU寄存器中占用空间....

最典型的,比如CPU在时间片切换的时候,那么没有处理完数据,放到哪里?当然是CPU的相关存储空间中。时间片大概30ms左右。

【2】用户堆栈模式:用户程序中“局部变量”和“参数传递”所使用的堆栈。常见的错误:StackOverFlowException《堆栈溢出》,如果你写一死循环,一定会出现这种情况,可以试一下。

因为操作系统默认会分配1M的空间给用户堆栈。用于局部变量和参数传递。

4.线程在时间上的开销

【1】 资源使用通知的开销:因为一个程序会调用很多的托管的和非托管的dll或exe资源.... 线程销毁呢?同样也得通知。就比如,下图中的资源不是想用就能用的,必须通知后才能调用。

【2】时间片切换开销。

 输入q,表示离开

5.线程生命周期管理

运行一个多线程程序,点击start,再点击suspend

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace D02_2_ThreadLifeCycle
{
    public partial class FrmThread : Form
    {
        public FrmThread()
        {
            InitializeComponent();
        }

        private Thread thread = null;
        private int counter = 0;

        //【1】开启
        private void btnStart_Click(object sender, EventArgs e)
        {
            thread = new Thread(() =>
             {
                 while (true)
                 {
                     try
                     {
                        Thread.Sleep(500);
                         lblInfo.Invoke(new Action(() =>
                         {
                             lblInfo.Text += counter++ + ",";
                         }));
                     }
                     catch (Exception ex)
                     {
                         MessageBox.Show(ex.Message + "  异常位置:" + counter++);
                     }
                 }
             });
            thread.Start();
        }
        //暂停
        private void btnSuspend_Click(object sender, EventArgs e)
        {
            if (thread.ThreadState == ThreadState.Running ||
                thread.ThreadState == ThreadState.WaitSleepJoin)
            {
                thread.Suspend();
            }
        }
        //继续
        private void btnResume_Click(object sender, EventArgs e)
        {
            if (thread.ThreadState == ThreadState.Suspended )
            {
                thread.Resume();
            }
        }
        //中断
        private void btnInterrupt_Click(object sender, EventArgs e)
        {
            thread.Interrupt();
        }
        //终止
        private void btnAbort_Click(object sender, EventArgs e)
        {
            thread.Abort();
        }

    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace D02_2_ThreadLifeCycle
{
    public partial class FrmThread : Form
    {
        public FrmThread()
        {
            InitializeComponent();
        }

        private Thread thread = null;
        private int counter = 0;

        //【1】开启
        private void btnStart_Click(object sender, EventArgs e)
        {
            thread = new Thread(() =>
             {
                 while (true)
                 {
                     try
                     {
                        Thread.Sleep(500);
                         lblInfo.Invoke(new Action(() =>
                         {
                             lblInfo.Text += counter++ + ",";
                         }));
                     }
                     catch (Exception ex)
                     {
                         MessageBox.Show(ex.Message + "  异常位置:" + counter++);
                     }
                 }
             });
            thread.Start();
        }
        //暂停
        private void btnSuspend_Click(object sender, EventArgs e)
        {
            if (thread.ThreadState == ThreadState.Running ||
                thread.ThreadState == ThreadState.WaitSleepJoin)
            {
                thread.Suspend();
            }
        }
        //继续
        private void btnResume_Click(object sender, EventArgs e)
        {
            if (thread.ThreadState == ThreadState.Suspended )
            {
                thread.Resume();
            }
        }
        //中断
        private void btnInterrupt_Click(object sender, EventArgs e)
        {
            thread.Interrupt();
        }
        //终止
        private void btnAbort_Click(object sender, EventArgs e)
        {
            thread.Abort();
        }

    }
}

 

 

 按照之前的步骤,输入:!ThreadState ab024,看到了User Suspend Pending信息,说明当前线程处于Suspend状态。

最后再点击Abort,退出后,再次进入查看线程状态,OSID已经为0,说明已经Dead,且此时的线程状态已经变为Aborted 

 

 6.Task和ThreadPool的使用

(1)开启线程的三种方式:Start(),Run(),Factory启动

        #region Task启动的三种方式 
        private static void TaskStart()
        {
            //方式一:Start()
            //Task task1 = new Task(()=> {
            //    Thread.Sleep(1000);
            //    Console.WriteLine("子线程1ID:"+Thread.CurrentThread.ManagedThreadId);
            //});
            //task1.Start();
            //方式二:Run()
            //Task task2 = Task.Run(()=>{
            //    Console.WriteLine("子线程2ID:"+Thread.CurrentThread.ManagedThreadId);
            //});
            //方式三:Factory启动
            Task task3 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("子线程3ID:"+ Thread.CurrentThread.ManagedThreadId);
            });
            Console.WriteLine("主线程ID:"+ Thread.CurrentThread.ManagedThreadId);
        }
        #endregion

输出结果:

主线程ID:1
子线程3ID:3

(2)线程的阻塞:WaitAny()、WaitAll()、Wait()方法,线程的阻塞就是必须当线程执行完之后,才会继续执行其他线程。

【1】Wait()方法

    
        //阻塞Task线程的Wait方法
        private static void TaskWait()
        {
            Task task1 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("我是Task1子线程:" + DateTime.Now.ToLongTimeString());
            });
            Task task2 = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("我是Task2子线程:" + DateTime.Now.ToLongTimeString());
            });
            task1.Wait();//等待task1执行完
            task2.Wait();//等待task2执行完
            Thread.Sleep(1000);
            Console.WriteLine("我是主线程" + DateTime.Now.ToLongTimeString());
        }
     

输出结果:可以看到使用了Wait方法之后,task1执行完后才会执行task2,task2执行完后才会执行主线程。

我是Task2子线程:15:38:26
我是Task1子线程:15:38:26
我是主线程15:38:27

【2】WaitAll()方法

       //阻塞Task线程的WaitAll方法:子线程不按顺序
        private static void TaskWaitAll()
        {
            Task task1 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("我是Task1子线程:" + DateTime.Now.ToLongTimeString());
            });
            Task task2 = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("我是Task2子线程:" + DateTime.Now.ToLongTimeString());
            });
            Task.WaitAll(task1,task2);//等待task1和task2执行完再执行主线程(就是void main中的线程)
            Thread.Sleep(1000);

            Console.WriteLine("我是主线程" + DateTime.Now.ToLongTimeString());
        }

输出可结果:task1和task2的执行顺序是未知的,先执行完task1和task2之后才会执行主线程。

我是Task2子线程:15:42:59
我是Task1子线程:15:42:59
我是主线程15:43:00

【3】WaitAny()方法

       //阻塞线程的WaitAny方法
        private static void TaskWaitAny()
        {
            Task task1 = Task.Run(() =>
            {
                Thread.Sleep(1000);

                Console.WriteLine("我是子线程Task1:" + DateTime.Now.ToLongTimeString());
            });
            Task task2 = Task.Run(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine("我是子线程Task2:" + DateTime.Now.ToLongTimeString());
            });
            Task.WaitAny(task1, task2);//只要有一个Task完成就执行主线程
            Console.WriteLine("我是主线程:" + DateTime.Now.ToLongTimeString());
        }

输出结果:task1和task2只要有一个执行完,就会执行主线程。

我是子线程Task1:15:45:00
我是主线程:15:45:00
我是子线程Task2:15:45:01

(2)Task任务的取消

【1】只取消任务

        private static void TaskCancel()
        {
            //创建取消任务信号源对象
            CancellationTokenSource ct = new CancellationTokenSource();
            Task task1 = Task.Run(() =>
            {
                while (!ct.IsCancellationRequested)//判断任务是否取消
                {
                    Thread.Sleep(200);
                    Console.WriteLine("我是子线程Task1" + "\t" + DateTime.Now.ToLongTimeString());
                }
            }, ct.Token);
            //先执行主线程
            Thread.Sleep(4000);//4秒后取消
            ct.Cancel();
 Console.WriteLine("结束子线程,开始执行主线程" + "\t" + DateTime.Now.ToLongTimeString()); Console.WriteLine(
"我是主线程" + "\t" + DateTime.Now.ToLongTimeString()); }

输出结果:当执行 ct.Cancel()这句后,结束task1线程执行的任务。

我是子线程Task1 15:55:22
我是子线程Task1 15:55:23
我是子线程Task1 15:55:23
我是子线程Task1 15:55:23
我是子线程Task1 15:55:23
我是子线程Task1 15:55:23
我是子线程Task1 15:55:24
我是子线程Task1 15:55:24
我是子线程Task1 15:55:24
我是子线程Task1 15:55:24
我是子线程Task1 15:55:24
我是子线程Task1 15:55:25
我是子线程Task1 15:55:25
我是子线程Task1 15:55:25
我是子线程Task1 15:55:25
我是子线程Task1 15:55:25
我是子线程Task1 15:55:26
我是子线程Task1 15:55:26
我是子线程Task1 15:55:26
结束子线程,开始执行主线程      15:55:26
我是主线程      15:55:26
我是子线程Task1 15:55:26

【2】取消任务的同时触发一个任务

        //取消任务的同时触发一个任务
        private static void TaskCancel2()
        {
            //创建取消任务信号源
            CancellationTokenSource ct = new CancellationTokenSource();
            Task task1 = Task.Run(() =>
            {
                while (!ct.IsCancellationRequested)
                {
                    Thread.Sleep(200);
                    Console.WriteLine("我是子线程Task1" + "\t" + DateTime.Now.ToLongTimeString());
                }
            });
            //注册一个委托,在取消任务时调用该委托
            ct.Token.Register(() =>
            {
                Console.WriteLine("清理任务中");
                Thread.Sleep(100);
                Console.WriteLine("清理任务完成");
            });
            
            ct.CancelAfter(5000);//设定超时时间为5秒
            Thread.Sleep(8000);//8秒后取消任务
            ct.Cancel();//取消任务,同时调用注册的委托
         
            Console.WriteLine("超时时间5秒,超时自动取消");
        }

输出结果:尽管有Thread.Sleep(8000);//8秒后取消任务,但是由于设定了5秒后取消任务,所以,线程还是执行5秒后就结束了任务。

我是子线程Task1 16:06:13
我是子线程Task1 16:06:13
我是子线程Task1 16:06:13
我是子线程Task1 16:06:14
我是子线程Task1 16:06:14
我是子线程Task1 16:06:14
我是子线程Task1 16:06:14
我是子线程Task1 16:06:14
我是子线程Task1 16:06:15
我是子线程Task1 16:06:15
我是子线程Task1 16:06:15
我是子线程Task1 16:06:15
我是子线程Task1 16:06:15
我是子线程Task1 16:06:16
我是子线程Task1 16:06:16
我是子线程Task1 16:06:16
我是子线程Task1 16:06:16
我是子线程Task1 16:06:16
我是子线程Task1 16:06:17
我是子线程Task1 16:06:17
我是子线程Task1 16:06:17
我是子线程Task1 16:06:17
我是子线程Task1 16:06:17
我是子线程Task1 16:06:18
清理任务中
我是子线程Task1 16:06:18
清理任务完成
超时时间5秒,超时自动取消

(3)Task的延续

        private static void TaskWhenAll()
        {
            Task task1 = Task.Factory.StartNew(() =>
            {
                //Thread.Sleep(100);//加上这句执行顺序是:主线程----->task2--->task1
                Console.WriteLine("我是子线程Task1" + "\t" + DateTime.Now.ToLongTimeString());
            });
            //带参数
            Task task2 = Task.Factory.StartNew((arg) =>
            {
                Console.WriteLine(arg + "\t" + DateTime.Now.ToLongTimeString());
            }, "我是子线程Task2");

            //Task.WhenAll的延续:主线程不等待(主线程先执行),子线程全部执行完之后才会执行后面的任务,如果需要主线程依次执行,请将主线程的代码添加进(延续任务)task3中即可
            Task.WhenAll(task1, task2).ContinueWith(task3 =>
            {
                Console.WriteLine("我是延续任务Task3" + "\t" + DateTime.Now.ToLongTimeString());
                //Console.WriteLine("我是主线程" + "\t" + DateTime.Now.ToLongTimeString());
            });
            Console.WriteLine("我是主线程" + "\t" + DateTime.Now.ToLongTimeString());
        }

输出结果:

我是子线程Task2 16:18:25
我是子线程Task1 16:18:25
我是主线程      16:18:25
我是延续任务Task3       16:18:25
        private static void TaskWhenAny()
        {
            Task task1 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("我是子线程Task1" + "\t" + DateTime.Now.ToLongTimeString());
            });
            //带参数
            Task task2 = Task.Factory.StartNew((arg) =>
            {
                Console.WriteLine(arg + "\t" + DateTime.Now.ToLongTimeString());
            }, "我是子线程Task2");
            //Task.WhenAny的延续:主线程不等待(主线程先执行),子线程只要有一个执行完就执行后面的线程(主线程和延续任务执行顺序不定),如果需要主线程依次执行,请将主线程的代码添加进(延续任务)task3中即可
           Task.WhenAny(task1, task2).ContinueWith(task3 => { Console.WriteLine("我是延续任务3" + "\t" + DateTime.Now.ToLongTimeString()); }); Console.WriteLine("我是主线程" + "\t" + DateTime.Now.ToLongTimeString());
}

输出结果:

我是子线程Task2 16:15:44
我是主线程      16:15:44
我是延续任务3   16:15:44
我是子线程Task1 16:15:45

(4)长时间的耗时任务:如果一个线程的任务是非常耗时的,例如长时间的数据读取操作。

       //长时间运行任务:LongRunning
        private static void TaskLongRunning()
        {
            Task task1 = new Task(() =>
            {
                for (int i = 0; i < 5000; i++)
                {
                    Thread.Sleep(2000);
                    Console.WriteLine("我是子线程Task1" + "\t" + DateTime.Now.ToLongTimeString());
                }
            }, TaskCreationOptions.LongRunning);//长时间运行任务加上LongRunning
            task1.Start();
        }

(5)父子任务运行

        //父子任务运行
        private static void TaskCreationOption()
        {
            //父任务 TaskCreationOptions.AttachedToParent:把子线程附加到父线程中
            Task taskParent = new Task(() =>
            {
                //带参数
                Task task1 = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("我是子线程Task1" + "\t" + DateTime.Now.ToLongTimeString());

                }, TaskCreationOptions.AttachedToParent);
                //不带参数
                Task task2 = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("我是子线程Task2" + "\t" + DateTime.Now.ToLongTimeString());
                }, TaskCreationOptions.AttachedToParent);
            });
            taskParent.Start();
            taskParent.Wait();//相当于Task.Wait(task1,task2),如果不添加TaskCreationOptions.AttachedToParent枚举主线程会直接运行不等待
            Console.WriteLine("我是主线程" + "\t" + DateTime.Now.ToLongTimeString());
        }

输出结果:

我是子线程Task1 16:35:20
我是子线程Task2 16:35:20
我是主线程      16:35:20

(6)Task的异常处理

        #region Task线程异常处理:
        private static void TaskException()
        {
            //AggregateException:是一个异常集合,因为Task中可能抛出异常,所以我们需要新的类型来收集异常对象
            Task taskParent = Task.Factory.StartNew(() =>
            {
                Task task1 = Task.Factory.StartNew(() =>
                  {
                      throw new Exception("task1 Exception happened!");//抛出异常,AggregateException异常
                  }, TaskCreationOptions.AttachedToParent);
                Task task2 = Task.Factory.StartNew(() =>
                {
                    throw new Exception("task2 Exception happened!");//抛出异常,AggregateException异常
                }, TaskCreationOptions.AttachedToParent);
            });
            try
            {
                try
                {
                    taskParent.Wait();//等待附加的子线程执行结束,抛出两个线程的异常
                }
                catch (AggregateException ex)//捕捉异常,AggregateException是一个异常的集合
                {
                    foreach (var item in ex.InnerExceptions)//上面的两个异常在这里遍历得到
                     {
                        Console.WriteLine(item.InnerException.Message + item.GetType().Name);//item.GetType().Name=AggregateException
                    }
                    //3.异常集合,如果你想往上抛,需要用handle处理一下
                    ex.Handle(p => {
                        if (p.InnerException.Message == "task1 Exception happened!")
                            return true;//返回true表示处理了异常
                        else //p.InnerException.Message == "task2 Exception happened!"
                            return false;//表示向上抛出异常,第二个抛出异常,被最外侧的try catch捕捉到
                    });
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("-----------------------------------------------------");
                Console.WriteLine(ex.InnerException.InnerException.Message+ex.GetType().Name);
            }
        }

        #endregion

 

按F10继续下一步

 

 继续按F10

task1 Exception happened!AggregateException
task2 Exception happened!AggregateException
-----------------------------------------------------
task2 Exception happened!AggregateException

(7)线程锁:Lock:多线程之间必定会争夺资源

       #region 线程锁Lock:多线程之间必定会争夺资源

        private static int num = 0;
        private static object MyLock = new object();
        private static void TaskLock()
        {
            for (int i = 0; i < 5; i++)
            {
                //一次创建5个线程,线程的执行顺序不能确定
                Task task = Task.Run(() =>
                {
                    TestMethod();
                   // Console.WriteLine();
                });
            }
        }
        private static void TestMethod()
        {
            for (int i = 0; i < 5; i++)
            {
                //lock (MyLock)//锁住资源,保证里面的资源执行完再执行下一个线程
                {
                    num++;
                    Console.WriteLine(num);
                }
            }
        }
        #endregion

如果没有使用Lock锁,执行的结果:输出是无序的,因为有时间片的切换

1
3
4
5
6
2
7
8
9
10
11
12
14
15
16
17
18
19
20
21
13
22
23
24
25

如果使用Lock线程锁,输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

 (8)线程池的使用

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处

于多线程单元中。

       //线程池不带参数
        private static void Method1() {
            ThreadPool.QueueUserWorkItem((arg)=> 
            {
                Thread.Sleep(2000);
                Console.WriteLine("子线程"+Thread.CurrentThread.ManagedThreadId);
            });
            Console.WriteLine("主线程" + Thread.CurrentThread.ManagedThreadId);
        }
        //线程池带参数
        private static void Method2()
        {
            ThreadPool.QueueUserWorkItem((arg) =>
            {
                Console.WriteLine("子线程" + arg);
            }, Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("主线程" + Thread.CurrentThread.ManagedThreadId);
        }

 

posted @ 2021-04-01 22:51  WellMandala  阅读(91)  评论(0编辑  收藏  举报