C# - 多线程
首先大概讲述一下多线程和多进程的区别,任务管理器里各种不同的进程就是多进程,或者是你同时运行多个”.exe’程序就可以理解为多进程,多进程是要更多消耗CPU资源的。
多线程是相对于进程里更小的单位,比如3个线程在1个进程里进行不同的操作,他们所拥有的资源就是那个进程里的资源,不会占用更多资源,只是通过线程更合理的分配资源。
好,大概区分清楚两者后我们进入线程模式!
线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。
下面列出了线程生命周期中的各种状态:
- 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。
- 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。
- 不可运行状态:下面的几种情况下线程是不可运行的:
- 已经调用 Sleep 方法【指定睡眠时间后重启】
- 已经调用 Wait 方法【】
- 通过 I/O 操作阻塞
- 死亡状态:当线程已完成执行或已中止时的状况。
在很久以前的MS-DOS时代时代,都是单任务的,比如说我想玩王者荣耀和听你的答案,很抱歉,只能选择玩王者荣耀或者听你的答案,不能同时进行。
所以我只能先玩王者荣耀,玩腻了就关掉游戏打开音乐播放器听一首《你的答案》来舒缓心绪,模拟的程序如下:
using System; namespace ThreadStudy { class Program { static void Main(string[] args) { game(); music(); Console.ReadKey(); } public static void game() { for (int i = 0; i < 10; i++) { Console.WriteLine($"第{i}次执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 玩王者荣耀"); } } public static void music() { for (int i = 0; i < 10; i++) { Console.WriteLine($"第{i}次执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 听你的答案"); } } } }
控制台输出的结果为:
第0次执行:2021-04-01 21:26:30 玩王者荣耀
第1次执行:2021-04-01 21:26:30 玩王者荣耀
第2次执行:2021-04-01 21:26:30 玩王者荣耀
第3次执行:2021-04-01 21:26:30 玩王者荣耀
第4次执行:2021-04-01 21:26:30 玩王者荣耀
第5次执行:2021-04-01 21:26:30 玩王者荣耀
第6次执行:2021-04-01 21:26:30 玩王者荣耀
第7次执行:2021-04-01 21:26:30 玩王者荣耀
第8次执行:2021-04-01 21:26:30 玩王者荣耀
第9次执行:2021-04-01 21:26:30 玩王者荣耀
第0次执行:2021-04-01 21:26:30 听你的答案
第1次执行:2021-04-01 21:26:30 听你的答案
第2次执行:2021-04-01 21:26:30 听你的答案
第3次执行:2021-04-01 21:26:30 听你的答案
第4次执行:2021-04-01 21:26:30 听你的答案
第5次执行:2021-04-01 21:26:30 听你的答案
第6次执行:2021-04-01 21:26:30 听你的答案
第7次执行:2021-04-01 21:26:30 听你的答案
第8次执行:2021-04-01 21:26:30 听你的答案
第9次执行:2021-04-01 21:26:30 听你的答案
当然,我们现在的时代大可不必那么老实的先干什么再干什么,我们可以选择边玩游戏,边听歌。模拟代码如下:
using System; using System.Threading; namespace ThreadStudy { class Program { static void Main(string[] args) { Thread gameThread = new Thread(game); Thread musicThread = new Thread(music); gameThread.Start(); musicThread.Start(); Console.ReadKey(); } public static void game() { for (int i = 0; i < 10; i++) { Console.WriteLine($"第{i}次执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 玩王者荣耀"); } } public static void music() { for (int i = 0; i < 10; i++) { Console.WriteLine($"第{i}次执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 听你的答案"); } } } }
控制台输出的结果:
第0次执行:2021-04-01 21:30:42 听你的答案
第0次执行:2021-04-01 21:30:42 玩王者荣耀
第1次执行:2021-04-01 21:30:42 听你的答案
第2次执行:2021-04-01 21:30:42 听你的答案
第3次执行:2021-04-01 21:30:42 听你的答案
第4次执行:2021-04-01 21:30:42 听你的答案
第5次执行:2021-04-01 21:30:42 听你的答案
第6次执行:2021-04-01 21:30:42 听你的答案
第7次执行:2021-04-01 21:30:42 听你的答案
第1次执行:2021-04-01 21:30:42 玩王者荣耀
第2次执行:2021-04-01 21:30:42 玩王者荣耀
第8次执行:2021-04-01 21:30:42 听你的答案
第3次执行:2021-04-01 21:30:42 玩王者荣耀
第9次执行:2021-04-01 21:30:42 听你的答案
第4次执行:2021-04-01 21:30:42 玩王者荣耀
第5次执行:2021-04-01 21:30:42 玩王者荣耀
第6次执行:2021-04-01 21:30:42 玩王者荣耀
第7次执行:2021-04-01 21:30:42 玩王者荣耀
第8次执行:2021-04-01 21:30:42 玩王者荣耀
第9次执行:2021-04-01 21:30:42 玩王者荣耀
运用线程后玩游戏和听音乐可以同时展开,两个任务不分先后时间差明显比单线程模式要少,更合理的利用了资源。
其实以上的代码中还隐藏了一个线程,就是主线程Main(string[] args),我们下面再改进一下代码看看三个线程运行时的情况。
核心代码更改如下:
using System; using System.Threading; namespace ThreadStudy { class Program { static void Main(string[] args) { Thread gameThread = new Thread(game); Thread musicThread = new Thread(music); gameThread.Start(); musicThread.Start(); Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}边玩王者荣耀边听你的答案"); Console.ReadKey(); } public static void game() { for (int i = 0; i < 10; i++) { Console.WriteLine($"第{i}次执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 玩王者荣耀"); } } public static void music() { for (int i = 0; i < 10; i++) { Console.WriteLine($"第{i}次执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 听你的答案"); } } } }
控制台输出结果:
第0次执行:2021-04-01 21:34:30 玩王者荣耀
2021-04-01 21:34:30边玩王者荣耀边听你的答案
第0次执行:2021-04-01 21:34:30 听你的答案
第1次执行:2021-04-01 21:34:30 听你的答案
第2次执行:2021-04-01 21:34:30 听你的答案
第1次执行:2021-04-01 21:34:30 玩王者荣耀
第2次执行:2021-04-01 21:34:30 玩王者荣耀
第3次执行:2021-04-01 21:34:30 玩王者荣耀
第3次执行:2021-04-01 21:34:30 听你的答案
第4次执行:2021-04-01 21:34:30 玩王者荣耀
第5次执行:2021-04-01 21:34:30 玩王者荣耀
第4次执行:2021-04-01 21:34:30 听你的答案
第5次执行:2021-04-01 21:34:30 听你的答案
第6次执行:2021-04-01 21:34:30 听你的答案
第7次执行:2021-04-01 21:34:30 听你的答案
第8次执行:2021-04-01 21:34:30 听你的答案
第9次执行:2021-04-01 21:34:30 听你的答案
第6次执行:2021-04-01 21:34:30 玩王者荣耀
第7次执行:2021-04-01 21:34:30 玩王者荣耀
第8次执行:2021-04-01 21:34:30 玩王者荣耀
第9次执行:2021-04-01 21:34:30 玩王者荣耀
线程的优先级可以通过Thread类Priority属性设置,Priority属性是一个ThreadPriority型枚举,列举了5个优先等级:AboveNormal、BelowNormal、Highest、Lowest、Normal。
普通线程的优先级默认为Normal;如果想有更高的优先级,可设置为AboveNormal或Highest;如果想有较低的优先级,可设置为BelowNormal或Lowest。
也许有人会问那之前的例子主线程总是先完成是不是优先级默认要高呢?其实不是的,主线程和工作线程的优先级相同,也是交替进行,被执行的概率大体相同,至于每次先完成是因为主线程会先启动,只有启动了主线程才能开启其他工作线程。
要注意的是:系统优先执行优先级较高的线程,但这只意味着优先级较高的线程占有更多的CPU时间,并不意味着一定要先执行完优先级较高的线程,才会执行优先级较低的线程。
在多线程编程中,可能会有多个线程并发的(或同时)执行一段代码,但是某些情况下需要在同一时刻只能有一个线程执行,避免某些对象的调用冲突或内存使用冲突,这就需要用到锁(lock)。lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。在同一个时刻内只允许一个线程进入执行,而其他线程必须等待。
using System; using System.IO; using System.Threading; namespace ThreadStudy { class Program { private static readonly object obj = new object(); static void Main(string[] args) { Thread th1 = new Thread(TestLock1); Thread th2 = new Thread(TestLock2); th1.IsBackground = true;//该线程为后台线程。后台线程将会随着主线程的退出而退出。 th2.IsBackground = true; th2.Start(); th1.Start(); Console.ReadKey(); } private static void TestLock1() { lock (obj) { int i = 0; while (true) { i++; var message = $"TestLock1:{i}"; string logFileName = @"H:\Log\" + DateTime.Now.ToString("yyyyMMdd") + "Log" + ".txt"; if (i < 100 || i > 200) { StreamWriter sr = new StreamWriter(logFileName, true); try { sr.WriteLine(message); } catch { Console.WriteLine(message); } finally { sr.Close(); } } if (i > 300) return; } } } private static void TestLock2() { lock (obj) { int i = 0; while (true) { i++; var message = $"TestLock2:{i}"; string logFileName = @"H:\Log\" + DateTime.Now.ToString("yyyyMMdd") + "Log" + ".txt"; if (i >= 100 || i <= 200) { StreamWriter sr = new StreamWriter(logFileName, true); try { sr.WriteLine(message); } catch { Console.WriteLine(message); } finally { sr.Close(); } } if (i > 300) return; } } } } }
Thread.Sleep(毫秒);
在while(true)死循环中,也能起到作用
using System; using System.Threading; namespace ThreadStudy { class Program { static void Main(string[] args) { Thread gameThread = new Thread(game); Thread musicThread = new Thread(music); gameThread.Start(); musicThread.Start(); Console.ReadKey(); } public static void game() { int i = 0; while (true) { Console.WriteLine($"第{i}次执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 玩王者荣耀"); i++; if (i == 5) Thread.Sleep(3000);//5秒 if (i > 10) return; } } public static void music() { int i = 0; while (true) { Console.WriteLine($"第{i}次执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 听你的答案"); i++; if (i > 10) return; } } } }
控制台输出结果:
测试代码:
using System; using System.Threading; namespace ThreadStudy { class Program { static void Main(string[] args) { Thread test = new Thread(Test); test.Start(); Console.ReadKey(); } public static void Test() { int i = -2; while (true) { i++; try { Console.WriteLine($"结果{i}:{10 / i}"); if (i > 10) return; } catch (Exception ex) { Console.WriteLine($"异常:{ex.Message}"); } } } } }