代码改变世界

.Net 线程 Thread 类

2021-08-09 10:10  石吴玉  阅读(731)  评论(0编辑  收藏  举报

注解

进程启动时,公共语言运行时将自动创建单个前台线程以执行应用程序代码。 除了此主前台线程,进程还可以创建一个或多个线程来执行与进程关联的程序代码的一部分。 这些线程可以在前台或后台执行。 此外,还可以使用 ThreadPool 类来执行由公共语言运行时管理的工作线程上的代码。

 

1、前台线程和后台线程

类的实例 Thread 表示前台线程或后台线程。 后台线程与前台线程完全相同,但有一种例外情况:如果所有前台线程均已终止,后台线程并不会使进程保持运行。 所有前台线程停止后,运行时将停止所有后台线程并关闭。

默认情况下,以下线程在前台执行:

  • 主应用程序线程。

  • 通过调用类构造函数创建的所有线程 Thread 。

默认情况下,以下线程在后台执行:

  • 线程池线程,线程池是由运行时维护的工作线程池。 您可以通过使用类来配置线程池和计划在线程池线程上工作 ThreadPool 。

 

2、暂停与阻塞

示例代码

namespace JsonTest
{
    public class Program
    {
        public static void Main()
        {
            Thread thread = new Thread(OneTest);
            thread.Name = "小弟弟";

            Console.WriteLine($"{DateTime.Now}:大家在吃饭,吃完饭后要带小弟弟逛街");
            Console.WriteLine("吃完饭了");
            Console.WriteLine($"{DateTime.Now}:小弟弟开始玩游戏");
            thread.Start();

            // 化妆 5s
            Console.WriteLine("不管他,大姐姐化妆先"); 
            Thread.Sleep(TimeSpan.FromSeconds(5));

            Console.WriteLine($"{DateTime.Now}:化完妆,等小弟弟打完游戏");
            thread.Join();

            var temp = thread.IsAlive;
            Console.WriteLine("打完游戏了嘛?" + (!thread.IsAlive ? "true" : "false"));
            Console.WriteLine($"{DateTime.Now}:走,逛街去");
            Console.ReadLine();
        }

        public static void OneTest()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "开始打游戏");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"{DateTime.Now}:第几局:" + i);
                Thread.Sleep(TimeSpan.FromSeconds(2));      // 休眠 2 秒
            }
            Console.WriteLine(Thread.CurrentThread.Name + "打完了");
        }
    }
}
View Code

执行结果:

Sleep()方法(不会释放锁)

sleep()让当前正在执行的线程先暂停一定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,处于sleep()中的线程也不会执行。当sleep()结束后,线程会转入到Runnable(就绪状态),这样才能够得到执行的机会。

sleep(0)  强制当前线程放弃剩余的CPU时间片

放弃当前线程所剩余的CPU时间片就意味着其他比此线程优先级高或者相同且可以被调度的线程将会在此时刻被调用。

如果当前系统环境下其他可以被调度的线程优先级都比此线程低,实际上此线程还是优先执行。

Join()方法

Thread的非静态方法,join让一个线程等待另一个线程完成才继续执行。如果线程A执行体中调用线程B的 join() 方法,则A线程将会被阻塞,直到线程B调用完成为止,A才能得以继续执行。

Yield() 方法 (线程让步,不会释放锁)--实际效果与 sleep(0) 类似

让一个线程执行Yield()方法后,就会立刻进入Runnable(就绪状态);除此之外,yield()方法还与线程优先级有关,当某个线程调用 yield() 方法时,就会从运行状态切换到就绪状态,之后cpu从就绪状态线程队列中只会选择与该线程优先级相同或者更高优先级的线程去执行。

 

3、线程状态

ThreadState 是一个枚举,记录了线程的状态,我们可以从中判断线程的生命周期和健康情况。

 

枚举 说明
Running 0 此状态指示线程当前正在使用处理器
StopRequested 1 线程被停止,这仅供内部使用
SuspendRequested 2 线程被请求挂起
Background 4 该线程作为后台线程执行,而不是前台线程。 此状态通过设置 System.Threading.Thread.IsBackground 属性来控制
Unstarted 8 该System.Threading.Thread.Start方法尚未调用线程。
Stopped 16 线程已停止。
WaitSleepJoin 32 线程被阻塞。 这可能是调用 System.Threading.Thread.Sleep(System.Int32) 或 System.Threading.Thread.Join 请求锁定的结果 - 例如,通过调用 System.Threading.Monitor.Enter(System.Object) 或 System.Threading.Monitor.Wait(System.Object,System.Int32,System.Boolean) - 或等待线程同步对象,如 System.Threading.ManualResetEvent
Suspended 64 该线程已被挂起。
AbortRequested 128 System.Threading.Thread.Abort(System.Object) 方法已在线程上调用,但线程尚未收到将尝试终止它的挂起 System.Threading.ThreadAbortException。
Aborted 256

线程状态包括 System.Threading.ThreadState.AbortRequested 和
线程现在已死,但其状态尚未更改为 System.Threading.ThreadState.Stopped。

4、线程终止

.Abort() 方法不能在 .NetCore上使用,不然会出现   

System.PlatformNotSupportedException:“Thread abort is not supported on this platform.”

 

5、线程的不确定性

线程的不确定性是指几个并行运行的线程,不确定CPU时间片会分配给谁(当然,分配有优先级)。

对我们来说,多线程是同时运行的,但一般CPU没有那么多核,不可能再同一时刻执行所有的线程。CPU会决定某个时刻时间片分配给多线程中的一个线程,这就出现了CPU时间片分配调度。

 

6、自旋和休眠

当线程进入休眠状态或者解除休眠状态时,会发生上下文切换,这就带来了昂贵的消耗。

而线程不断运行,就会消耗CPU时间占用CPU资源。

对于过短的等待,应该使用自旋(spin)方法,避免发生上下文切换;过长的等待应该是线程休眠,避免占用大量的CPU时间.

我们最为熟知的 Sleep() 方法休眠线程。

自旋的意思是:没事找事做,通过做一些简单的运算,来消耗时间,从而达到等待的目的。c# 中自旋和自旋锁使用 Thread.SpinWait()方法

SpinWait() 实质上是(处理器)使用了非常紧密的循环,并使用 iterations 参数指定循环计数。SpinWait等待时间取决于处理器的速度。