多线程进阶
进程
当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。
线程
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
多线程的好处:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
多线程的不利方面:
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
线程太多会导致控制太复杂,最终可能造成很多Bug。
Thread类有几个至关重要的方法,描述如下:
Start():启动线程;
Sleep(int):静态方法,暂停当前线程指定的毫秒数;
Abort():通常使用该方法来终止一个线程;
Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复;
Resume():恢复被Suspend()方法挂起的线程的执行;
下面我们就动手来创建一个线程,使用Thread类创建线程时,只需提供线程入口即可。在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。 在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。如果你想在你的应用程序中使用多线程,就必须包含这个类。Thread类有一个静态属性CurrentThread用于获取了当前执行的线程。不管你创建了多少个这个类的实例,CurrentThread只有一个,因此很容易理解属性CurrentThread是静态的了。虽然有多个线程同时存在,但是在某一个时刻,CPU只能执行其中一个。 我们通过其中提供的Thread类来创建和控制线程,ThreadPool类用于管理线程池等。
下面我们就动手来创建一个线程,使用Thread类创建线程时,只需提供线程入口即可。在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。
下面我们先来看一种最简单:不需要传递参数,也不需要返回参数
using System;
using System.Threading;
class Greeting
{
public void GreetingPerson1()
{
Console.WriteLine("Hello guowenhui");
}
public void GreetingPerson2()
{
Console.WriteLine("Hello guowenjia");
}
public void GreetingPerson3()
{
Console.WriteLine("Hello dongyaguang");
}
}
class Test
{
public static void Main()
{
Thread.CurrentThread.Name="System Thread"; //给当前线程起名为"System Thread"
Console.WriteLine(Thread.CurrentThread.Name+" Status:"+Thread.CurrentThread.ThreadState);
Greeting greeting=new Greeting();
ThreadStart threadStart=new ThreadStart(greeting.GreetingPerson1);
threadStart=threadStart+greeting.GreetingPerson2;
threadStart=threadStart+greeting.GreetingPerson3;
Thread thread=new Thread(threadStart);
thread.Start();
}
}
运行结果:
Hello guowenhui
Hello guowenjia
Hello dongyaguang
需要传递单个参数:
上面的代码中ThreadStart定义为void ThreadStart();也就是说所执行的方法不能带有参数,这显然是一个很大的缺陷,.NET为了解决这个问题,设计出了另一种ThreadStart,这就是ParameterizedThreadStart ,首先说一下,ParamenterizedThreadStart的定义为void ParamenterizedThreadStart(object obj).下面我们对上面的代码进行修改。
using System;
using System.Threading;
class Greeting
{
public void GreetingPerson(object name)
{
Console.WriteLine("Hello " + (string)name);
}
}
class Test
{
public static void Main()
{
Greeting greeting = new Greeting();
ParameterizedThreadStart paramthreadStart = new ParameterizedThreadStart(greeting.GreetingPerson);
Thread thread1 = new Thread(paramthreadStart);
thread1.Start("guowenhui");
Thread thread2 = new Thread(paramthreadStart);
thread2.Start("guowenjia");
Console.WriteLine("The Thread Is Over!");
}
}
这里有一点我们需要注意,ParamenterizedThreadStart所传入的参数为object型,且参数只能有一个。因此在定义注册到该委托的方法的时候,参数只能有一个,且为object型,在方法体中使用该该参数的时候一般都需要进行转型。当然,将参数定义为object型不是完全欠考虑的,当我们需要传入多个参数的时候,object就能发挥其威力了,通过把多个参数组合到一个类中,然后把这个类的实例作为参数传递,就可以实现多个参数传递。
using System;
using System.Threading;
class Parms
{
public int id;
public string name;
public Parms(int id, string name)
{
this.id = id;
this.name = name;
}
}
class Greeting
{
public void GreetingPerson(object obj)
{
Parms parms = (Parms)obj;
Console.WriteLine(parms.name+": your studID is "+parms.id);
}
}
class Test
{
public static void Main()
{
Greeting greeting = new Greeting();
ParameterizedThreadStart paramthreadStart = new ParameterizedThreadStart(greeting.GreetingPerson);
Thread thread = new Thread(paramthreadStart);
Parms parms = new Parms(91103061,"guowenhui");
thread.Start(parms);
}
}
虽然通过把需要的参数包装到一个类中,委托ParameterizedThreadStart就可以传递多个参数,但是由于这个委托的传入参数是object,所以不可避免的需要进行参数转换,那么有没有更好的方法了,使用专门的线程类
这是许多程序员喜欢使用的经典模式,简单来说,就是把需要另起线程执行的方法,和它需要的参数放到一个类中,参数作为了类的属性,调用时声明此类的实例,然后初始化属性,方法执行时直接使用类里初始化好的属性来执行,这样方法本身就可以不需要参数,而又起到了多参数传递的效果,于是使用本文最开始提到的不带参数的ThreadStart委托就可以了,并且由于需要执行的方法和参数都放在一个类中,充分体现了面向对象的特点.具体方法如下。
using System;
using System.Threading;
class Parms
{
public int _id;
public string _name;
public Parms(int id, string name)
{
this._id = id;
this._name = name;
}
public int ID
{
set{_id=value;}
get{return _id;}
}
public string Name
{
set{_name=value;}
get{return _name;}
}
public void GreetingPerson()
{
Console.WriteLine(this.Name+": your studID is "+this.ID);
}
}
class Test
{
public static void Main()
{
Parms parms=new Parms(91103061,"guowenhui");
ThreadStart threadStart=new ThreadStart(parms.GreetingPerson);
Thread thread=new Thread(threadStart);
thread.Start();
}
}
需要传递参数且需要返回参数
既然涉及到多线程,那主线程需要知道分线程什么时候完成计算,才能访问分线程的计算结果,主线程需要知道子线程什么时候执行完成,可以使用Thread.ThreadState枚举来判断,当线程的
threadState=threadState.Stop,说明线程已经完成了工作,这时候线程返回的结果就可以用了,倘若需要等有多个子线程的返回,并且需要用他们的结果来进行进异步计算,那就叫做线程同步了,下面我们介绍另外一种我比较推荐的方法,能够自定义参数个数,并且返回数据,而且使用起来也相对方便,即使用委托的异步方法调用和回调。
线程池
线程虽然是个好东西,但是也是个资源消耗大户,许多时候,我们需要用多线程,但是又不希望线程的数量过多,这就是线程池的作用,.Net为我们提供了现成的线程池ThreadPool,他的使用如下
using System;
using System.Threading;
namespace ConsoleApplication1
{
class Parms
{
public int id;
public string name;
public Parms(int id, string name)
{
this.id = id;
this.name = name;
}
}
class Greeting
{
public static void GreetingPerson(object obj)
{
Parms parms = (Parms)obj;
Console.WriteLine(parms.name+": your studID is "+parms.name);
}
}
class Test
{
public static void Main()
{
WaitCallback callback = new WaitCallback(Greeting.GreetingPerson);
ThreadPool.QueueUserWorkItem(callback,new Parms(91103061,"guowenhui"));
}
首先定义一个WaitCallback委托,WaitCallback的格式是void WaitCallback(object state),也就是说你的方法必须符合这个格式,接着调用QueueUserWorkItem,将这个任务加入线程池,当县城池有空闲线时,将会调度并运行你的代码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架