C#线程入门
Posted biyusr216
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 | 重新启动挂起的线程 |
**安全点:**
安全点是指代码中公共语言运行时可以安全地执行自动“垃圾回收”的位置。
垃圾回收是指释放不再使用的变量并回收内存的过程。调用线程的 Abort 或 Suspend 方法时,公共语言运行时将对代码进行分析,确定让线程停止运行的适当位置。
Thread常用属性:
属性 | 说明 |
IsAlive | 如果线程处于活动状态,则返回TRUE |
IsBackground | 是否是后台线程 |
Name | 获取或设置线程的名称。通常用于在调试时发现各个线程。 |
Priority | 获取或设置操作系统用于确定线程调度优先顺序的值 |
ApartmentState | 获取或设置用于特定线程的线程模型。线程模型在线程调用非托管代码时很重要。 |
ThreadState | 线程状态 |
IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池。 |
ManagedThreadId | 获取当前托管线程的唯一标识符。 |
每个线程都有一个优先级属性,用于确定其执行所占用的处理器时间片大小。操作系统为高优先级线程分配较长的时间段,并为低优先级线程分配较短的时间段。新创建的线程具有值 Normal
,但可以将 Priority 属性更改为 ThreadPriority 枚举中的任何值
。
以上就是Thread类常用属性即方法,其他用法可以查看Thread底层。
线程同步:
所谓同步:实质就是在某一时刻,只有一个线程可以访问变量;如果不能确保对变量的访问是同步的,就会产生错误。
C#为同步访问变量提供了一个非常简单的方式,即使用“Lock”关键词,它可以把一段代码定义为互斥段,互斥段在某一个时刻内,只允许一个线程进入执行,而其他线程必须等待。Lock使用语法如下:
Lock(expression)
statement_block
来个实例,就拿药店卖药为例子:假设药店的“999感冒灵”总量是固定的,然后分发到各个药店出售,一个药店卖出一包,总量就会减少一包,
再不考虑线程同步的情况下,
示例如下:
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”,stocknum 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”,stocknum pack"); else Console.WriteLine("stock nothing!"); while (num > 0);//判断是否有库存,如果有就可以持续出售
运行结果如下:
从运行结果可以看出,在线程同步的情况下,每个药店都从库存里面拿药,药店1出售的时候,药店2就等着,当药店1销售完最后一包药,就没有另外的销售记录了,无论是药店1还是药店2来拿药,库存都没有了,这就是线程同步的重要性!
结论:expression代表你希望跟踪的对象:
如果你想保护一个类的实例,一般地,你可以使用this;
如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了
而statement_block就算互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
以上就是线程的入门级教程~
不积跬步,无以至千里;不积小流,无以成江海。ヾ(◍°∇°◍)ノ゙
以上是关于C#线程入门的主要内容,如果未能解决你的问题,请参考以下文章