线程基础必知必会(二)(上)

简介: 线程基础必知必会(二)

这篇文章将在上篇文章的基础上,进一步讲解线程的相关知识。这篇文章涉及到的知识点有 线程优先级前台与后台线程线程参数lock、Monitor 线程异常处理 。这篇要比上一篇难度有一点提高,但是大家不用担心,我依然会用大量的代码来展示每个知识点,并且对于其中的难点我会详细讲解。下面我们就开始学习基础知识的第二篇。


一、线程优先级

.NET 给我们定义了多种线程优先级,它们都位于 ThreadPriority 中,分别是: Lowest BelowNormal NormalAboveNormalHighest 。它们的优先级和说明如下表所示:

image.png在普通的情况下,如果优先级高的线程在运行,就不会给优先级低的线程分配任何 CPU 时间,这样就可以保证重要/主要线程具有较高的优先级。在大多数的时间内,这个线程什么也不做,而其他线程则执行它们的任务。一旦有信息输入,这个线程就立即获得比其他线程更高的优先级,在短时间内处理输入的信息。根据我在项目中的经验来看,高优先级的线程一般用在 处理用户输入数据重要数据处理 应用程序主线程 。下面我们通过一个例子来看一下线程的优先级。

using System.Threading;
using static System.Console;
namespace Thread_Priority
{
    class Program
    {
        static int printNumberRunCount = 0;
        static int printStringRunCount = 0;
        static void Main(string[] args)
        {
            Process.GetCurrentProcess().ProcessorAffinity = new System.IntPtr(1);
            Thread printNumberThread = new Thread(PrintNumber);
            Thread printStringThread = new Thread(PrintString);
            printNumberThread.Priority = ThreadPriority.Highest;
            printStringThread.Priority = ThreadPriority.Lowest;
            printNumberThread.Start();
            printStringThread.Start();
            Thread.Sleep(2000);
            printNumberThread.Abort();
            printStringThread.Abort();
            Write($"PrintNumber 循环了:{printNumberRunCount} 次,printStringThread 循环了:{printStringRunCount} 次");
            Read();
        }
        static void PrintNumber()
        {
            for (int i = 0; i < 10000; i++)
            {
                printNumberRunCount++;
                WriteLine($"输出数字:{i}");
            }
        }
        static void PrintString()
        {
            for(int i=0;i<10000;i++)
            {
                printStringRunCount++;
                WriteLine("are you ok?");
            }
        }
    }
}

在上面的代码中我们创建了两个线程 printNumberThreadprintStringThread 这两个线程分别调用 PrintNumberPrintString 方法。并且我们也定义了两个统计线程运行次数的变量 printNumberRunCount printStringRunCount。之后我们将 printNumberThread 线程的优先级设置为最高 Highest ,将 printStringThread 线程的优先级设置为最低 Lowest ,接着我们在运行线程两秒后将线程停掉,这时我们从控制台输出的内容可以看出来高优先级的线程 printNumberThread 循环的次数大于低优先级的线程 printStringThread 循环的次数。 在代码中有这么一行 Process.GetCurrentProcess().ProcessorAffinity = new System.IntPtr(1);这段代码的意思是告诉操作系统将所有的线程都在 CPU 的第一个核心上进行运算。加这么一段代码只是为了将优先级更明显的表现出来而已,实际开发中除非特殊情况,一般不这么写。

image.png

Tip:

优先级越高所占用的 CPU 时间就会越多。但是即使我们手动设置最高的优先级,也不会超过操作系统进程的优先级。

二、前台与后台线程

前台线程和后台线程大体上是一样的,唯一的不同是进程中所有的前台线程都完成工作完后,就会马上结束进程工作,即使还有后台线程在工作。简单的说就是后台线程不会确保进程一直运行,当进程中的所有前台线程都停止,系统会关闭所有后台线程。我们可以通过 Thread IsBackground 属性来设置线程是前台线程还是后台线程,当复制为 True 时表示时后台线程,繁反之为前台线程。这里需要注意的是属于线程池的线程是后台线程,从非托管代码进入托管执行环境的线程都会变为后台线程,默认情况下通过新建并启动 Thread 对象生成的所有线程都是前台线程。

using System.Threading;
using static System.IO.File;
using static System.Console;
namespace ForegroundBackgroundThread
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread printNumberThread = new Thread(PrintNumber);
            Thread printStringThread = new Thread(PrintString);
            printStringThread.IsBackground = true;
            printNumberThread.Start();
            printStringThread.Start();
        }
        static void PrintNumber()
        {
            for (int i = 0; i < 20; i++)
            {
                AppendAllText("PrintNumber.txt",i+"\r\n");
            }
        }
        static void PrintString()
        {
            for(int i=0;i<50;i++)
            {
                AppendAllText("PrintString.txt", i + "\r\n");
            }
        }
    }
}

上述代码中我们定义了两个线程 printNumberThread printStringThread ,并分别调用 PrintNumber PrintString 方法。其中 PrintNumber 方法我们循环输出 20 个数字,PrintString 方法我们循环输出 50 个数字。然后我们将 printStringThread 线程通过属性 IsBackground 设置为后台线程,最后启动这两个线程。在代码运行完毕后我们来查看一下输出的两个文件 PrintNumber.txt PrintString.txt 中的内容。

1.png

从两个文件的内容可以看出,PrintNumber 文件输出了所有的数字,而 PrintString 却没有输出所有数字,这是因为 printStringThread 为后台线程,当 printNumberThread 线程执行完毕后进程就推出了。

那么到这里会有很多读者要问了,前后台线程有什么用呢?后台线程适用于后台任务,例如将被动侦听活动的线程设置为后台线程,将负责发送数据的线程设置为前台线程,这样在所有的数据发送完毕之后台前线程不会被终止。前台线程用于需要长时间等待的任务,例如监听客户端请求。后台线程用于处理时间较短的任务,例如处理客户端发送的请求。


三、线程参数

前面我们创建线程调用的方法都是不带参数的,但是在实际开发中线程调用的方法不带参数的情况很少,大部分情况都是带有参数的,那么遇到这种情况我们该怎么处理呢?我先来看一下代码。

using System.Threading;
using static System.Console;
namespace ThreadPara
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(()=>PrintNumber(50));
            thread.Start();
            Read();
        }
        static void PrintNumber(int number)
        {
            for (int i = 0; i < number; i++)
            {
                WriteLine($"输出数字:{i}");
            }
        }
    }
}

上述代码中我们利用匿名方法调用 PrintNumber 方法,并将参数传递进来。除了这种方法还有另一种方式传递参数,通过 Thread.Start(para) 传递参数,代码如下:

using System.Threading;
using static System.Console;
namespace ThreadPara
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(PrintNumber);
            thread.Start(50);
            Read();
        }
        static void PrintNumber(object number)
        {
            for (int i = 0; i < (int)number; i++)
            {
                WriteLine($"输出数字:{i}");
            }
        }
    }
}
目录
相关文章
|
2月前
|
Java 调度
Java并发基础-线程简介(状态、常用方法)
Java并发基础-线程简介(状态、常用方法)
26 0
|
3月前
|
存储 安全 Linux
【探索Linux】P.19(多线程 | 线程的概念 | 线程控制 | 分离线程)
【探索Linux】P.19(多线程 | 线程的概念 | 线程控制 | 分离线程)
28 0
|
3月前
|
C#
C#学习系列相关之多线程(二)----Thread类介绍
C#学习系列相关之多线程(二)----Thread类介绍
|
Java API
java并发原理实战(3) -- 线程的中断和初始化
java并发原理实战(3) -- 线程的中断和初始化
214 0
java并发原理实战(3) -- 线程的中断和初始化
|
Java API Spring
java并发原理实战(4) -- 线程的创建方式
java并发原理实战(4) -- 线程的创建方式
112 0
java并发原理实战(4) -- 线程的创建方式
C#编程-147:线程基础
C#编程-147:线程基础
102 0
C#编程-147:线程基础
|
API C#
C#多线程(14):任务基础②
C#多线程(14):任务基础②
190 0
C#多线程(14):任务基础②
|
编译器 C# Windows
线程基础必知必会(一)
线程基础必知必会(一)
155 0
线程基础必知必会(一)
|
Java 中间件 程序员
面经手册 · 第20篇《Thread 线程,状态转换、方法使用、原理分析》
大部分考试考的,基本都是不怎么用的。例外的咱们不说😄 就像你做程序开发,尤其在RPC+MQ+分库分表,其实很难出现让你用一个机器实例编写多线程压榨CPU性能。很多时候是扔出一个MQ,异步消费了。如果没有资源竞争,例如库表秒杀,那么其实你确实很难接触多并发编程以及锁的使用。 但!凡有例外,比如你需要开发一个数据库路由中间件,那么就肯定会出现在一台应用实例上分配数据库资源池的情况,如果出现竞争就要合理分配资源。如此,类似这样的中间件开发,就会涉及到一些更核心底层的技术的应用。
229 0
面经手册 · 第20篇《Thread 线程,状态转换、方法使用、原理分析》
|
存储 API C#
C#多线程(15):任务基础③
任务基础一共三篇,本篇是第三篇,之后开始学习异步编程、并发、异步I/O的知识。 本篇会继续讲述 Task 的一些 API 和常用的操作。
152 0