多线程 学习
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); }