01、什么是线程?
要深刻明白什么是线程,就必要相识计算机的发展史,必要相识多任务概念,必要相识历程概念,然后才是线程概念。由于我们重要还是讲解线程,因此这里就不举行睁开说其他概念了,有兴趣的可以自行相识下。
简朴来说,线程就是操纵系统中能够单独实行任务的最小单元。
对于大多数编程语言来说,都有一个或者类似的功能的Main()方法,而该方法中的所有代码也都是按照次序一行一行的实行,如果要想实行下一行代码那么就必须等待上一行代码实行完成。而线程作为能够单独实行任务的最小单元,因此线程可以使得应用程序的一部分独立于另外一部分而单独运行,这也就意味着我们可以改变程序的常规代码实行次序,从而到达更复杂的程序控制。
对于一个应用程序来说,要想正常运行至少要有一个线程,通常为主线程,也就是上文中提到的Main()方法,当调用此方法时系统就会主动创建一个主线程。
02、背景线程和前台线程
线程可以分为前台线程和背景线程,两者基本完全相同,唯一区别是前台线程可以在托管实行环境中一直运行,而背景线程不可以。简朴来说就是当历程中所有前台历程都停止后,系统会主动停止并关闭该历程内的所有背景线程。
在C#中可以通过Thread.IsBackground检察当火线程范例,也可以通过该属性修改线程范例。默认情况下,通过Thread对象新建并启动的所有线程都是前台线程。
看如下简朴示例:- public static void CreateThread()
- {
- Console.WriteLine($"主线程 是否为后台线程:{Thread.CurrentThread.IsBackground}");
- var thread1 = new Thread(()=> Console.WriteLine("Hello World"));
- Console.WriteLine($" 线程1 默认为后台线程:{thread1.IsBackground}");
- thread1.IsBackground = true;
- Console.WriteLine($" 线程1 设置为后台线程:{thread1.IsBackground}");
- thread1.Start();
- }
复制代码 实行效果如下:
03、线程的优先级
线程作为操纵系统中能够单独实行任务的最小单元,那么当一个历程中有多个线程时,应该先实行那个线程呢?因此线程必要一个标记其实行优先级的属性。
在C#中Thread可以通过Priority来设置线程的优先级,告诉系统应该先实行谁。ThreadPriority有以下5种范例:
- Lowest: 最低优先级,在所有优先级中最低,在所有线程中可位于末了实行。
- BelowNormal: 低于正常优先级,在Normal优先级之后,在Lowest优先级之前。
- Normal: 默认优先级,线程默认的优先级
- AboveNormal: 高于正常优先级,在Highest优先级的线程之后,在Normal优先级之前。
- Highest: 最高优先级,在所有优先级中最高,在所有线程中可优先实行。
下面我们做个简朴的测试,用来验证优先级不同的导致差异。- class ThreadPriorityTest
- {
- //是否执行,确保一个线程修改此值后,其他线程立刻查看到最新值
- static volatile bool isRun = true;
- //确保每个线程都有独立的副本存储计数统计值
- [ThreadStatic]
- static long threadCount;
- //停止运行
- public void Stop()
- {
- isRun = false;
- }
- //打印线程名称对应优先级以及计数总数
- public void Print()
- {
- threadCount = 0;
- while (isRun)
- {
- threadCount++;
- }
- Console.WriteLine($"{Thread.CurrentThread.Name} 优先级为{Thread.CurrentThread.Priority,8} 总执行计数为:{threadCount,-13:N0}");
- }
- }
- public static void PriorityTest()
- {
- var threadPriorityTest = new ThreadPriorityTest();
- //创建3个线程,并设置优先级
- var thread1 = new Thread(threadPriorityTest.Print)
- {
- Name = "线程1"
- };
- var thread2 = new Thread(threadPriorityTest.Print)
- {
- Name = "线程2",
- Priority = ThreadPriority.Lowest
- };
- var thread3 = new Thread(threadPriorityTest.Print)
- {
- Name = "线程3",
- Priority = ThreadPriority.Highest
- };
- //启动3个线程
- thread1.Start();
- thread2.Start();
- thread3.Start();
- //休眠3秒
- Thread.Sleep(10000);
- //停止运行
- threadPriorityTest.Stop();
- //等待所有线程完成
- thread1.Join();
- thread2.Join();
- thread3.Join();
- }
复制代码 实行效果如下:
可以发现优先级越高,其实行计数值越大。
其中必要注意的是volatile和ThreadStatic的用法。
在这个多线程示例中我们必要准确的统计不同的线程实行计数,因此正常来说可能必要设置多个变量用来对应存储各自线程的统计计数,很显然这样会导致代码臃肿。因此我们选用了另一种办法,使用ThreadStatic标记一个字段,使得该字段对每个线程都有独立的副本。这样可以做到线程之间不会共享这个字段的值,同时还可以做到多个线程只用这一个字段。
另外对于多线程共享的变量,很可能由于CPU缓存导致多个线程共享的变量不同等题目,因此通过volatile告诉编译器和运行时每次访问该字段时都要直接从内存中读取最新值,以此来包管线程之间的可见性。
04、线程的生命周期
当一个线程被创建后,会经历多个状态,包括未启动、已启动、实行、睡眠、挂起等十个状态,同时Thread类也提供了一些方法,用来控制当火线程状态,好比启动、停止、恢复、停止、挂起以及等待线程等方法。
我们先来看看线程具体有哪些状态:
- Running(运行)—— 线程已启动,而且没有停止;
- StopRequested(哀求停止) —— 哀求停止线程;
- SuspendRequested(哀求挂起) —— 哀求线程挂起;
- Background(背景) —— 线程在背景实行;
- Unstarted(未启动) —— 还没有在线程上调用 Start()方法;
- Stopped(停止) —— 线程已完成了其所有的指令,而且已经停止;
- WaitSleepJoin(等待睡眠毗连) —— 通过调用 Wait()、Sleep()或 Join()方法,来停息线程;
- Suspended(挂起) —— 线程处于挂起状态;
- AbortRequested(哀求停止) —— Abort()方法已调用,但是线程还没有收到试图终止自己的 System.Threading.ThreadAbortexception,也就是说,线程还没有停止但不久就会停止;
- Aborted****(停止) —— 线程处于停止状态,但不一定已实行完毕;
下面我们再来看看线程的常用方法。
- Start(): 启动线程,使其状态变动为Running。
- Sleep(): 把正在运行的线程停息一段时间后主动恢复,线程状态保持活跃。
- Suspend():[已弃用]停息当火线程的实行,直到调用 Thread.Resume 显式恢复。
- Resume():[已弃用]恢复一个已被停息的线程。
- Interrupt(): 停止处于 WaitSleepJoin 线程状态的线程。
- Join(): 阻塞调用线程,直到某个线程终止时为止。
- Abort():[已弃用]终止当火线程。
通过源码可以看到Resume和Suspend方法被弃用的原因。这是由于它们有很多题目和缺陷。使用它可能会导致程序的不稳定、死锁或者资源竞争题目。因此,它已经被标记为废弃,不保举再使用。
在多线程编程中,通常可以通过合理的同步机制来控制线程的实行。好比,使用上述的 Monitor、Mutex、Event 和 Semaphore 来协调多个线程的行为,确保资源访问的安全和精确性。
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |