基础知识
1) 一个应用程序就是一个进程,一个进程中至少有一个线程,线程可分为前台线程和后台线程。
2) 前台线程和后台线程
3) 一个人一边烧水一边洗衣服比“先烧水再洗衣服”效率高。同一时刻一个人只能干一件事情,其实是在“快速频繁切换”,如果处理不当可能比不用多线程效率还低。讨论多线程先只考虑“单核 cpu”。
4) 普通的代码是从上向下执行的,但是多线程的代码可以“并行”执行,可以把“线程”理解成独立的执行单元,线程中的代码可以“并行执行”。
线程根据情况被分配给一定的“时间片”来运行,可能一个线程还没执行完,就又要把时间片交给别的线程执行。把要在单独的线程放到一个方法中,然后创建 Thread 对象,运行它,这个 Thread中的代码就会在单独的线程中执行。
5) 多线程的好处:
有很大可能增加系统的运行效率;
开发 winform 程序,避免界面卡;
注册后向用户发送欢迎邮件,如果发送邮件很慢的话,避免注册过程很慢
1 static void Main(string[] args) 2 { 3 4 int i = 5; 5 //创建一个子线程 6 Thread t1 = new Thread(() => 7 { 8 //返回到主线程中 9 Console.WriteLine("i="+i); 10 }); 11 t1.Start(); 12 Console.ReadLine(); 13 }
参数化线程
1 static void Main(string[] args) 2 { 3 4 int i = 5; 5 //创建一个子线程 6 Thread t1 = new Thread((obj) => 7 {//委托中添加参数 8 //返回到主线程中 9 Console.WriteLine("i=" + i); 10 Console.WriteLine("obj="+ obj); 11 }); 12 //参数化 13 t1.Start(i); 14 Console.ReadLine(); 15 }
创建的十个线程执行的顺序是不一定的,所以出现这样的结果
线程睡眠
Thread.Sleep(n)指当前代码所在的线程“睡眠 N 毫秒”
线程退出
前后台线程的主要区别:
进程中只要存在没有完成的前台线程,进程就不会被销毁。
换句话说就是,如果所有的前台线程都完成了,进程就会被销毁,即使是存在未完成任务的后台线程。
可以通过设置Thread.IsBackground来把线程设置为前台线程或者是后台线程。
把线程设置为“后台线程”后,所有“非后台线程”执行结束后程序就会退出,不会等“后台线程”执行结束
线程优先级
static void Main(string[] args) { Thread t1 = new Thread(()=> { Console.WriteLine("线程优先级"); }); //设置线程优先级,线程默认都是Normal级 t1.Priority = ThreadPriority.Normal; t1.Start(); } public enum ThreadPriority { Lowest = 0, BelowNormal = 1, Normal = 2, AboveNormal = 3, Highest = 4 }
线程的终止
可以调用 Thread 的 Abort 方法终止线程的执行,会在当前执行的代码上“无风起浪”的抛出 ThreadAbortException,可以 catch 一下验证一下,一般不需要程序去 catch。
线程的同步
1 class Program 2 { 3 private static int counter = 0; 4 static void Main(string[] args) 5 { 6 Thread t1 = new Thread(() => 7 { 8 for (int i = 0; i < 1000; i++) 9 { 10 counter++; 11 Thread.Sleep(1); 12 } 13 }); 14 t1.Start(); 15 Thread t2 = new Thread(() => 16 { 17 for (int i = 0; i < 1000; i++) 18 { 19 counter++; 20 Thread.Sleep(1); 21 } 22 }); 23 24 t2.Start(); 25 while (t1.IsAlive) { }; 26 while (t2.IsAlive) { }; 27 Console.WriteLine(counter); 28 Console.ReadKey(); 29 } 30 31 }
当t1线程占用counter时t2线程可能已经改变了counter的值,造成数据的不同步
解决方案:
①:lock关键字:对象互斥
②:[MethodImpl(MethodImplOptions.Synchronized)],只能有一个线程访问
③:Monitor类:lock关键字最终会被编译成Monitor
1 class Program 2 { 3 private static int counter = 0; 4 private static object locker = new object(); 5 static void Main(string[] args) 6 { 7 Thread t1 = new Thread(() => 8 { 9 for (int i = 0; i < 1000; i++) 10 { 11 lock (locker) 12 { 13 counter++; 14 } 15 Thread.Sleep(1); 16 } 17 }); 18 t1.Start(); 19 Thread t2 = new Thread(() => 20 { 21 for (int i = 0; i < 1000; i++) 22 { 23 lock (locker) { 24 counter++; 25 } 26 Thread.Sleep(1); 27 } 28 }); 29 30 t2.Start(); 31 while (t1.IsAlive) { }; 32 while (t2.IsAlive) { }; 33 Console.WriteLine(counter); 34 Console.ReadKey(); 35 } 36 }
线程中其它的操作
1、Interrupt 用于提前唤醒一个在 Sleep 的线程,Sleep 方法会抛出 ThreadInterruptedException 异常
1 Thread t1 = new Thread(()=> { 2 Console.WriteLine("t1要睡了"); 3 try 4 { 5 Thread.Sleep(5000); 6 } 7 catch(ThreadInterruptedException) 8 { 9 Console.WriteLine("擦,叫醒我干啥"); 10 } 11 Console.WriteLine("t1醒了"); 12 }); 13 t1.Start(); 14 Thread.Sleep(1000); 15 t1.Interrupt();
2、Sleep 是静态方法,只能是自己主动要求睡,别人不能命令他睡
3、已经过时的方法:Suspend、Resume。不要用
4、Abort()方法会强行终止线程,会引发线程内当前在执行的代码发出 ThreadAbortException异常
5、t1.Join()当前线程等待 t1 线程执行结束(Join 这里翻译成“连接”:你完了我再接着)
1 class Program 2 { 3 4 static void Main(string[] args) 5 { 6 Thread t1 = new Thread(() => 7 { 8 for (int i = 0; i < 100; i++) 9 { 10 Console.WriteLine("t1 " + i); 11 } 12 }); 13 t1.Start(); 14 Thread t2 = new Thread(() => 15 { 16 t1.Join();//等着 t1 执行结束 17 for (int i = 0; i < 100; i++) 18 { 19 Console.WriteLine("t2 " + i); 20 } 21 }); 22 t2.Start(); 23 } 24 }
单例模式与多线程
简单实用的,可在单线程也可在多线程中使用:
1 class God{ 2 private static God instance=new God(); 3 private God(){ 4 5 } 6 public static God GetInstance(){ 7 return instance; 8 } 9 }
懒汉模式:他的问题在于如果是多线程则可能有2个线程同时访问if(instance==null),则会出现问题
1 class God{ 2 private static God instance=null; 3 private God(){} 4 5 public static God GetInstance(){ 6 if(instance==null){ 7 instance=new God(); 8 } 9 return instance; 10 } 11 }
多线程下的懒汉模式:这种情况在每次创建时都访问lock是会造成性能下降
1 class God{ 2 private static God instance=null; 3 private God(){} 4 private static object locker=new object(); 5 6 public static God GetInstance(){ 7 lock(locker){ 8 if(instance==null){ 9 instance=new God(); 10 } 11 return instance; 12 } 13 } 14 }
多线程下双重锁单利:
1 class God{ 2 private static God instance=null; 3 private God(){} 4 private static object locker=new object(); 5 6 public static God GetInstance(){ 7 if(instance==null){ 8 lock(locker){ 9 if(instance==null){ 10 instance=new God(); 11 } 12 return instance; 13 } 14 } 15 } 16 }
WaitHandle
除了锁之外,.Net 中还提供了一些线程间更自由通讯的工具,他们提供了通过“信号”进行通讯的机制,通俗的比喻为“开门”、“关门”:Set()开门,Reset()关门,WaitOne()等着开门
WaitHandle抽象类
ManualResetEvent:手动开关
WaitOne():开一次门
1 ManualResetEvent mre=new ManualResetEvent(false);//默认值给的是否开门,false关门 2 Thread t1=new Thread(()=>{ 3 cw("开始等待着开门"); 4 mre.WaitOne();//开一次门 5 cw.("终于等到你开门"); 6 }); 7 t1.start(); 8 cw("按任意键开门"); 9 cr(); 10 mre.Set(); 11 cr(); 12 13 14 //WaitOne()还可以设置等待时间 15 //mre.Reset()手动关门
WaitAll():用来等待所有信号都变为“开门状态”
WaitAny():用来等待任意一个信号都变为“开门状态”。
AutoResetEvent:他是在开门并且一个 WaitOne 通过后自动关门,因此命名为“AutoResetEvent”(Auto 自动-Reset 关门)
1 AutoResetEvent are = new AutoResetEvent(false); 2 Thread t1 = new Thread(() => { 3 while (true) 4 { 5 Console.WriteLine("开始等着开门"); 6 are.WaitOne(); 7 Console.WriteLine("终于等到你"); 8 } 9 }); 10 t1.Start(); 11 Console.WriteLine("按任意键开门"); 12 Console.ReadKey(); 13 are.Set();//开门 14 Console.WriteLine("按任意键开门"); 15 Console.ReadKey(); 16 are.Set(); 17 Console.WriteLine("按任意键开门"); 18 Console.ReadKey(); 19 are.Set();
ManualResetEvent 就是学校的大门,开门大家都可以进,除非主动关门;
AutoResetEvent:就是火车地铁的闸机口,过了一个后自动关门