C# 中的多线程是计算机程序设计中的一种并发执行技术,允许在同一时间内在计算机上执行多个线程。多线程可以提高应用程序的性能,尤其是在处理大量数据或需要同时执行多个任务的情况下。每个线程可以看作是程序执行的独立流,它们可以并行运行,共享相同的内存空间和其他资源。
提到线程,我们就得把进程也拿来说说,进程和线程是两种概念,进程包含线程
进程和线程的区别:
定义:
进程是操作系统进行资源分配和调度的一个独立单位,是程序的一次执行实例。每个进程都有自己独立的内存空间,至少包含一个线程(主线程)。
线程是进程中的一个实体,是CPU调度和执行的单位,比进程更小的能独立运行的基本单位。线程自身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如执行栈),但它可以与同属一个进程的其他线程共享进程所拥有的全部资源。
资源拥有:
进程拥有独立的内存空间,一个进程至少有一个线程,线程间通信不需要进行复杂的数据交换。
线程共享所属进程的内存空间和资源,线程间通信可以直接访问进程的资源。
创建开销:
创建进程的开销比创建线程要大,因为操作系统需要分配独立的内存空间和系统资源。
线程的创建开销较小,因为它可以共享进程的资源。
执行方式:
进程是程序的执行流的独立实体,一个进程的执行不会影响其他进程。
线程是进程中的执行流,一个线程的执行可以影响同一进程中的其他线程。
地址空间:
进程拥有独立的地址空间,进程间的地址空间是相互独立的。
线程共享进程的地址空间,这使得线程间的通信更为方便。
上下文切换:
进程间的上下文切换开销较大,因为涉及到不同的内存空间和可能的CPU资源重新分配。
线程间的上下文切换开销较小,因为它们共享相同的内存空间。
系统调度:
进程是资源分配和调度的基本单位,操作系统通过进程来管理资源。
线程是系统调度的基本单位,操作系统通过线程来分配CPU时间。
独立性:
进程具有较高的独立性,一个进程崩溃不会直接影响到其他进程。
线程的独立性较低,一个线程的崩溃可能会影响到同一进程中的其他线程。
C# 中实现多线程的关键概念和方法:
-
Thread(线程) 类:C# 提供了
System.Threading.Thread
类来创建和管理线程。你可以通过继承Thread
类或使用Thread
类的构造函数来创建一个线程。 -
任务并行库(TPL):.NET Framework 4.0 引入了任务并行库,它提供了一个更高级的抽象来处理并发和异步编程。使用
System.Threading.Tasks.Task
类可以简化多线程编程。 -
线程池(ThreadPool):.NET 运行时提供了一个线程池,用于管理线程的生命周期。使用线程池可以避免创建和销毁线程的开销。
-
锁(Locks):当多个线程需要访问共享资源时,需要使用锁来同步访问,以避免竞态条件。C# 提供了多种同步原语,如
Monitor
、Mutex
、Semaphore
、ReaderWriterLockSlim
等。 -
死锁:在多线程编程中,需要小心避免死锁的发生。死锁发生在两个或多个线程相互等待对方释放资源。
-
线程局部存储:使用
ThreadLocal<T>
类可以为每个线程提供独立的数据副本,避免线程间的数据竞争。 -
取消和超时:在多线程编程中,可能需要取消正在执行的任务或设置超时限制。可以使用
CancellationToken
和Task
类的超时参数来实现。
在 C# 中,System.Threading.Thread 类用于线程的工作。它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程(UI线程)。
1.Thread(线程)
主线程也叫UI线程,System.Threading.Thread 类用于线程的工作,允许创建并访问多线程应用程序中的单个线程,程序开始执行时,主线程自动创建。
Thread:创建和控制线程,设置其优先级并获取其状态。
创建一个线程
static void Main(string[] args)
{
Thread th = Thread.CurrentThread;
th.Name = "主线程";
Console.WriteLine("这是{0}", th.Name);//这是主线程
Console.ReadKey();
}
Thread 类有很多属性和方法
属性
CurrentCulture | 获取或设置当前线程的区域性。 |
CurrentPrincipal | 获取或设置线程的当前负责人(对基于角色的安全性而言)。 |
CurrentThread | 获取当前正在运行的线程。 |
CurrentUICulture | 获取或设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源。 |
ExecutionContext | 获取 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 |
IsAlive | 获取指示当前线程的执行状态的值。 |
IsBackground | 获取或设置一个值,该值指示某个线程是否为后台线程。 |
IsThreadPoolThread | 获取指示线程是否属于托管线程池的值。 |
ManagedThreadId | 获取当前托管线程的唯一标识符。 |
Name | 获取或设置线程的名称。 |
Priority | 获取或设置指示线程的调度优先级的值。 |
ThreadState | 获取一个值,该值包含当前线程的状态 |
初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托。
public class Work
{
public static void Main()
{
//启动一个调用参数化静态方法的线程。
Thread thread = new Thread(Work.DoWork);//创建了一个新的线程对象 thread,并将 Work.DoWork 静态方法指定为线程的执行目标。
thread.Start(11);//启动线程,并传递一个参数 11给 DoWork 方法。
//启动一个调用参数化实例方法的线程。
Work work = new Work();//创建Work类的实例work
thread = new Thread(Work.DoMoreWork);//再次使用thread变量,重新创建一个线程,并将 work.DoMoreWork 实例方法指定为线程的执行目标。
thread.Start("AAA"); //启动线程,并传递一个参数 AAA给 DoWork 方法。
Console.ReadKey();
}
private static void DoWork(object a)//静态方法 DoWork,youge object了icing的参数
{
//显示 DoWork 方法接收到的数据。
Console.WriteLine("静态实例的过程"+a);//静态实例的过程11
}
private static void DoMoreWork(object a)//实例方法 DoWork,youge object了icing的参数
{
Console.WriteLine("实例线程的过程" + a);//实例线程的过程AAA
}
}
Thread常用的几种方法
-
Start()
: 启动线程。如果线程的目标是一个静态方法,可以直接调用Start()
;如果目标是一个实例方法,则需要提供一个参数。 -
Join()
: 等待线程终止。调用此方法会阻塞当前线程,直到调用Join()
的线程完成执行。 -
Sleep(毫秒数)
: 使当前线程暂停指定的毫秒数。这个方法属于Thread
类的静态方法。 -
Abort()
: 强制终止线程。使用这个方法可以立即停止线程,但可能会导致资源未正确释放。 -
Interrupt()
: 请求中断线程。这个方法会设置线程的中断状态,但不会立即停止线程。线程需要检查中断状态并相应地处理。 -
IsAlive
: 属性,用于检查线程是否仍在运行。 -
CurrentThread
: 属性,返回一个表示当前正在执行的线程的Thread
对象。 -
ThreadState
: 属性,返回一个ThreadState
枚举,表示线程的当前状态(如:运行、等待、停止等)。 -
Priority
: 属性,设置或获取线程的优先级。 -
Name
: 属性,设置或获取线程的名称。 -
ManagedThreadId
: 属性,获取一个唯一的标识符,用于标识线程。 -
ThreadStart
: 委托,定义线程启动时要调用的方法的签名。 -
ParameterizedThreadStart
: 委托,定义线程启动时要调用的方法的签名,并允许传递一个参数。 -
SetApartmentState(ApartmentState state)
: 设置线程的公寓状态,如单线程公寓(STA)或多线程公寓(MTA)。 -
GetApartmentState()
: 获取线程的当前公寓状态。 -
Thread.CurrentThread.CurrentCulture
和Thread.CurrentThread.CurrentUICulture
: 属性,分别用于获取或设置当前线程的文化信息和用户界面文化信息。 -
ResetAbort()
: 如果线程被中止,此方法可以清除中止请求。
这些方法提供了对线程生命周期和行为的控制,使开发者能够根据需要创建和管理多线程应用程序。在使用线程时,应该小心处理线程安全问题,避免死锁和资源竞争。
多线程Thread优点和缺点,如何解决Thread缺点?
优点:
-
提高性能:通过并行处理,可以提高应用程序的响应速度和处理能力,尤其是在多核处理器上。
-
改善用户体验:通过在后台线程中执行耗时操作,可以保持用户界面的响应性。
-
资源利用:在多核或多CPU系统上,多线程可以更有效地利用硬件资源。
-
简化编程模型:使用
ThreadPool
可以简化线程管理,因为线程的创建和销毁由系统自动处理。 -
灵活性:开发者可以根据需要创建任意数量的线程,以适应不同的任务和负载。
缺点:
-
复杂性:多线程编程比单线程编程更复杂,需要处理同步、死锁和竞态条件等问题。
-
资源竞争:多个线程可能会竞争同一资源,导致性能下降。
-
上下文切换开销:频繁的线程切换可能会增加CPU的开销,尤其是在大量线程的情况下。
-
调试困难:多线程程序往往更难调试,因为问题可能与线程的执行顺序有关。
-
不可预测性:线程的执行顺序是不可预测的,这可能导致难以追踪的bug。
如何解决 Thread
缺点:
-
使用线程池:通过使用
ThreadPool
而不是手动创建线程,可以减少线程创建和销毁的开销。 -
同步机制:使用锁(如
Mutex
、Monitor
、lock
语句)、信号量(Semaphore
)、事件(EventWaitHandle
)等同步机制来管理对共享资源的访问。 -
避免死锁:设计线程访问资源的策略,以避免死锁的发生。例如,总是以相同的顺序获取锁。
-
使用线程局部存储:使用
ThreadLocal<T>
存储每个线程的私有数据,避免不必要的同步。 -
限制线程数量:合理配置线程池的大小,避免创建过多的线程。
-
使用异步编程模型:利用
async
和await
以及Task
类,可以编写更简洁、易于理解和维护的并发代码。 -
使用高级并发集合:.NET 提供了多种线程安全的集合,如
ConcurrentBag
、ConcurrentDictionary
、ConcurrentQueue
等,它们可以减少对锁的需求。 -
避免共享状态:尽可能设计无状态的线程,减少共享状态的使用。
-
使用条件变量:在某些情况下,使用条件变量可以更优雅地处理线程间的协调问题。
-
测试和分析:使用工具和技术(如性能分析器、日志记录)来测试和分析多线程程序,以便发现和解决并发问题。
2.Threadpool(线程池)
在C#中,ThreadPool
是一个线程池,它提供了一个管理线程集合的机制,这些线程可以被用来执行任务。使用 ThreadPool
可以提高应用程序的性能和响应能力,因为它避免了频繁创建和销毁线程的开销。线程池中的线程是可重用的,这意味着它们可以在完成一个任务后被重新分配给另一个任务,从而提高了资源的利用率。
使用 ThreadPool
的基本步骤:
-
定义工作项:首先,你需要定义要执行的工作项。这通常是通过创建一个委托(
Delegate
)来完成的,该委托指定了要执行的方法。 -
使用
ThreadPool.QueueUserWorkItem
:使用ThreadPool.QueueUserWorkItem
方法将工作项排队到线程池中。这个方法接受一个WaitCallback
委托和一个可选的对象参数。 -
执行工作项:一旦工作项被排队,线程池将自动分配一个线程来执行它。工作项的执行是异步的,这意味着主线程可以继续执行而不需要等待工作项完成。
internal class Program
{
static void Main(string[] args)
{
//定义工作项
WaitCallback waitCallback = new WaitCallback(Work);
//将工作项排队到线程池
ThreadPool.QueueUserWorkItem(waitCallback,state: "buxixi");//这个state可以不加
Console.WriteLine("我是主线程");
Thread.Sleep(2000);
Console.WriteLine("我在2秒后完成");
Console.ReadLine();
}
private static void Work(object state)
{
Console.WriteLine("xixi,"+state);
}
}
请注意,ThreadPool.QueueUserWorkItem
方法是异步的,主线程会在工作项执行时继续执行。如果你需要等待工作项完成,可以使用 ManualResetEvent
、AutoResetEvent
或其他同步机制。
线程池的特点:
-
自动线程管理:
ThreadPool
会自动管理线程的创建和销毁。当没有可用的线程来执行任务时,ThreadPool
会创建新的线程,或者如果线程长时间处于空闲状态,它们可能会被销毁。 -
任务队列:
ThreadPool
使用一个内部队列来管理待执行的任务。这允许多个任务在单个或多个线程上异步执行。 -
线程优先级:
ThreadPool
允许设置线程的优先级,以控制线程的执行顺序。 -
最小和最大线程数:可以设置
ThreadPool
的最小和最大线程数,以控制线程池的大小。 -
工作项(Work Items):
ThreadPool
允许提交工作项(通常是委托)来执行。这些工作项可以是长时间运行的任务,也可以是短暂的任务。 -
异步操作:
ThreadPool
支持异步编程模式,可以通过Task
或BackgroundWorker
等类来实现。 -
回调机制:
ThreadPool
允许注册回调方法,在特定的线程池事件(如线程完成工作项)发生时调用。 -
等待所有任务完成:
ThreadPool
提供了方法来等待所有排队的工作项完成。 -
配置和监控:
ThreadPool
可以通过ThreadPool.SetMinThreads
、ThreadPool.SetMaxThreads
等方法进行配置,并通过ThreadPool.ThreadPoolGetAvailableThreads
等方法进行监控。
线程池的优点:
减少线程创建和销毁的开销。
线程重用,提高资源利用率。
简化线程管理,自动处理线程的生命周期。
线程池的缺点
线程池中的线程数量有限,过多的任务可能导致线程池饱和。
线程池中的线程是不可预测的,你无法控制哪个线程将执行哪个任务。
线程池中的线程共享相同的内存空间,因此需要考虑线程安全问题。