C#线程入门
前言,多线程在日常编码中经常会用,本文就主线程和子线程之间的关系做个大概的总结,若有差错,欢迎斧正。
首先打开任务管理器,查看当前运行的进程: 菜单栏右键选择“Task Manager(任务管理)”或 “Ctrl”+“Alt”+“Delete”点击“Task Manager(任务管理)”
从任务管理器里面可以看到当前所有正在运行的进程。
Tab点击“性能”:
可以看到当前正在运行的所有进程数“Processes”和线程数“Threads”
一般来说一个应用程序就对应一个进程,一个进程可有一个或多个线程即(>=1个线程),有且只有一个主线程。
进程和线程:
进程:是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。一个正在运行的应用程序在操作系统中被视为一个进程,进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。是应用程序的一个运行例程,是应用程序的一次动态执行过程。
线程:是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。
前台线程后后台线程:
前台线程:默认情况下创建的线程都是前台线程,只有所有的前台线程关闭才能完成程序关闭。只有将线程属性IsBackground设置为true,才是后台线程
后台线程:程序关闭,后台线程不管是否执行完都将关闭
示例如下:
static void Main(string[] args) { Console.OutputEncoding = Encoding.UTF8; //前台线程 Thread qtxc = new Thread(() => RunLoop(10)); qtxc.Name = "qiantai thread"; //后台线程 Thread htxc = new Thread(() => RunLoop(20)); htxc.Name = "houtai thread"; htxc.IsBackground = true; //将线程设置为后台线程 //启动线程 qtxc.Start(); htxc.Start(); } public static void RunLoop(int count) { //获取当前线程名称 string threadName = Thread.CurrentThread.Name; for (int i = 0; i < count; i++) { Console.WriteLine($"{threadName} index:{(i + 1).ToString()}"); //休眠1秒 Thread.Sleep(1000); } Console.WriteLine($"{threadName} complete"); }
代码中定义了一个循环打印的方法,其中前台线程打印10次,后台线程打印20次,运行结果如下:
从运行结果可以看出,前台线程打印完成后,后台线程未打印完,但是程序自动结束了。
然后调整参数,前台线程打印20次,后台线程打印10次:
class Program { static void Main(string[] args) { //前台线程 Thread qtxc = new Thread(() => RunLoop(20)); qtxc.Name = "qiantai thread"; //后台线程 Thread htxc = new Thread(() => RunLoop(10)); htxc.Name = "houtai thread"; htxc.IsBackground = true; //将线程设置为后台线程 //启动线程 qtxc.Start(); htxc.Start(); } public static void RunLoop(int count) { //获取当前线程名称 string threadName = Thread.CurrentThread.Name; for (int i = 0; i < count; i++) { Console.WriteLine($"{threadName} index:{(i + 1).ToString()}"); //休眠1秒 Thread.Sleep(1000); } Console.WriteLine($"{threadName} complete"); } }
运行结果如下:
从运行结果可以看出,后台线程打印完成后,前台线程未打印完继续打印,前台线程打印完成后,程序结束。
结论就是:前台线程结束时,不管后台线程是否结束,程序都将结束;后台线程结束时,若有前台线程再运行,程序将继续执行,等到前台线程执行完后,程序结束。
后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。
主线程和子线程之间的关系:
- 默认情况,在新开启一个子线程的时候,他是前台线程,只有将线程的IsBackground属性设为true;他才是后台线程
- 当子线程是前台线程,则主线程结束并不影响其他线程的执行,只有所有前台线程都结束,程序结束
- 当子线程是后台线程,则主线程的结束,会导致子线程的强迫结束
- 不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。
- 托管线程池中的线程都是后台线程
C#中线程的使用:
引入命名空间:System.Threading
Thread线程常用方法列表:
方法 | 说明 |
Start | 开始线程 |
Sleep | 使线程暂停指定的一段时间。如Thread.Sleep(3000),即线程暂停三秒后继续执行 |
Abort | 在线程到达安全点时,使其停止。 |
Suspend | 在线程到达安全点时,使其暂停。 |
Resume | 重新启动挂起的线程 |
安全点是指代码中公共语言运行时可以安全地执行自动“垃圾回收”的位置。
属性 | 说明 |
IsAlive | 如果线程处于活动状态,则返回TRUE |
IsBackground | 是否是后台线程 |
Name | 获取或设置线程的名称。 通常用于在调试时发现各个线程。 |
Priority | 获取或设置操作系统用于确定线程调度优先顺序的值 |
ApartmentState | 获取或设置用于特定线程的线程模型。 线程模型在线程调用非托管代码时很重要。 |
ThreadState | 线程状态 |
IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池。 |
ManagedThreadId | 获取当前托管线程的唯一标识符。 |
Normal
,但可以将 Priority 属性更改为 ThreadPriority 枚举中的任何值
。{
statement_block
}
class Program { static void Main(string[] args) { //开启两个线程来代表两个分店 Shop shop = new Shop(); //线程1,分店1 Thread thread1 = new Thread(shop.Sell); thread1.Name = "shop1"; //线程2,分店2 Thread thread2 = new Thread(shop.Sell); thread2.Name = "shop2"; //开启线程,开始卖药 thread1.Start(); thread2.Start(); Console.ReadKey(); } } public class Shop { /// <summary> /// 剩余“999感冒灵”的数量 /// </summary> public int num = 10; public void Sell() { do { string threadName = Thread.CurrentThread.Name; if (num > 0) { Thread.Sleep(1000); num -= 1; Console.WriteLine($"{threadName} sell a“999”,stock{num} pack"); } else { Console.WriteLine("stock nothing!"); } } while (num > 0);//判断是否有库存,如果有就可以持续出售 } }
运行结果:
从运行结果可以看出,在线程未同步的情况下,两家药店都在销售库存的药,但是店铺1销售的时候不知道库存没有了,于是出现了库存 “-1”的情况,显然是有问题的;
然后调整方案,加入线程同步,示例如下:
class Program { static void Main(string[] args) { //开启两个线程来代表两个分店 Shop shop = new Shop(); //线程1,分店1 Thread thread1 = new Thread(shop.Sell); thread1.Name = "shop1"; //线程2,分店2 Thread thread2 = new Thread(shop.Sell); thread2.Name = "shop2"; //开启线程,开始卖药 thread1.Start(); thread2.Start(); Console.ReadKey(); } } public class Shop { /// <summary> /// 剩余“999感冒灵”的数量 /// </summary> public int num = 10; public void Sell() { do { string threadName = Thread.CurrentThread.Name; lock (this) //使用lock关键字解决线程同步的问题 { if (num > 0) { Thread.Sleep(1000); num -= 1; Console.WriteLine($"{threadName} sell a“999”,stock{num} pack"); } else { Console.WriteLine("stock nothing!"); } } } while (num > 0);//判断是否有库存,如果有就可以持续出售 } }
运行结果如下:
从运行结果可以看出,在线程同步的情况下,每个药店都从库存里面拿药,药店1出售的时候,药店2就等着,当药店1销售完最后一包药,就没有另外的销售记录了,无论是药店1还是药店2来拿药,库存都没有了,这就是线程同步的重要性!
结论:expression代表你希望跟踪的对象:
如果你想保护一个类的实例,一般地,你可以使用this;
如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了
而statement_block就算互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
以上就是线程的入门级教程~