22C#线程基础知识1
1.Thread
什么是线程Thread
线程是一个可执行路径,它可以独立于其它线程执行。
每个线程都在操作系统的进程(Process) 内执行,而操作系统的进程提供了程序运行的独立环境。
单线程应用,在进程的独立环境里只跑一个线程,所以该线程拥有独占权。
多线程应用,单个进程中会跑多个线程,它们会共享当前的执行环境(尤其是内存)。
例如,一个线程在后台读取数据,另一个线程在数据到达后进行展示。
这个数据就被称作是共享的状态。
例子:
static void Main(string[] args)
{
Thread thread = new Thread(WriteY);//开辟了一个新的线程 Thread
thread.Start();//运行WriteY()
//同时主线程也做一些工作
for (int i = 0; i <= 1000; i++)
Console.Write("X");
Console.ReadKey();
}
public static void WriteY()
{
for (int i=0;i<=1000;i++)
Console.Write("Y");
}
执行结果:
为啥结果回xy有重复的交替输出?
在单核计算机上,操作系统必须为每个线程分配“时间片”(在Windows中通常为20毫秒)来模拟并发,从而导致重复的x和y块。
在多核或多处理器计算机上,这两个线程可以真正地并行执行(可能受到计算机上其他活动进程的竞争)。
在本例中,由于控制台处理并发请求的机制的微妙性,您仍然会得到重复的x和y块。
图示流程:
术语:线程被抢占
一个线程的执行与另外一个线程上代码的执行交织的那一点。线程在这个时候就可以称为被抢占了。实际上就是操作系统夺走了线程占用着的cpu。
线程的一些属性
线程一旦开始执行,IsAlive就是true,线程结束就变成false。
线程结束的条件就是:线程构造函数传入的委托结束了执行。
线程一旦结束,就无法再重启。
每个线程都有个Name属性,通常用于调试。线程Name只能设置一次,以后更改会抛出异常。
静态的Thread.CurrentThread属性,会返回当先执行的线程。
例子:CurrentThread
static void Main(string[] args)
{
//设置当前线程的名字
Thread.CurrentThread.Name = "Main Thread....";
Thread t = new Thread(WriteY);//开辟了一个新的线程 Thread
t.Name = "Y Thread...";
t.Start();//运行WriteY()
Console.WriteLine(Thread.CurrentThread.Name);
//同时主线程也做一些工作
for (int i = 0; i <= 1000; i++)
Console.Write("X");
Console.ReadKey();
}
public static void WriteY()
{
Console.WriteLine(Thread.CurrentThread.Name);
for (int i=0;i<=1000;i++)
Console.Write("Y");
}
输出结果:
Join方法和Sleep方法
Join()
调用Join()方法,可以等待另一个线程结束。类似awit进程同步。
比如,主线程调用 Join方法,主线程就会等待子线程执行结束后才继续执行。
例子:
static void Main(string[] args)
{
Thread t = new Thread(Go);//开辟了一个新的线程 Thread
t.Start();//运行
t.Join();
Console.WriteLine(" Thread t has ended!");
Console.ReadKey();
}
public static void Go()
{
for (int i=0;i<=1000;i++)
Console.Write("Y");
}
Sleep()
进行线程等待。
class Program
{
static Thread thread1, thread2;
static void Main(string[] args)
{
thread1 = new Thread(Go);
thread1.Name = "Thread1";
thread1.Start();
thread2 = new Thread(Go);
thread2.Name = "Thread2";
thread2.Start();
Console.ReadKey();
}
public static void Go()
{
Console.WriteLine($"\n Current thread:{Thread.CurrentThread.Name}");
if (Thread.CurrentThread.Name == "Thread1" && thread2.ThreadState != ThreadState.Unstarted)
thread2.Join();
Thread.Sleep(2000);
Console.WriteLine($"\n Current thread:{Thread.CurrentThread.Name}");
Console.WriteLine($"Thread1:{thread1.ThreadState}");
Console.WriteLine($"Thread2:{thread2.ThreadState}");
}
}
结果
添加超时
调用Join的时候,可以设置一个超时,用毫秒或者TimeSpan都可以。如果返回true,那就是线程结束了;如果超时了,就返回false。
Thread.Sleep()
方法会暂停当前的线程,并等一段时间。
注意:
Thread.Sleep(O)
这样调用会导致线程立即放弃本身当前的时间片,自动将CPU移交给其他线程。
Thread.Yield()
做同样的事,但是它只会把执行交给同一处理器上的其它线程。
当等待Sleep或Join的时候,线程处于阻塞的状态。
Sleep(O)
或Yield
有时在高级性能调试的生产代码中很有用。它也是一个很好的诊断工具,有助于发现线程安全问题。如果在代码中的任何地方插入Sleep(O)
或Yield
就破坏了程序,那么你工的程序几乎肯定有bug。
阻塞(Blocking)
如果线程的执行由于某种原因导致暂定,那么就认为该线程被阻塞了。例如在Sleep或者通过Join等待其他线程结束。
被阻塞的线程会立即将其处理器的时间片生成给其它线程,从此就不再消耗处理器时间,直到满足其解除阻塞条件为止。
可以通过Threadstate这个属性来判断线程是否处于被阻塞的状态:
bool blocked = (someThread.Threadstate & Threadstate.WaitSleepDoin) != 0;
Threadstate线程的状态
Threadstate的枚举值:
但是它大部分的枚举值都没什么用,下面的代码将Threadstate剥离为四个最有用的值之一:Unstarted、Running. WaitSleepJoin 和 Stopped
ThreadState属性可用于诊断的目的,但不适用于同步,因为线程状态可能会在测试ThreadState和对
该信息进行操作之间发生变化。
解除阻塞(UnBlocking)
当遇到下列四种情况的时候,就会解除阻塞:
阻塞条件被满足
操作超时(如果设置超时的话)
通过Thread.Interrupt()进行打断
通过Thread.Abort()进行中止
上下文切换
当线程阻塞或解除阻塞时,操作系统将执行上下文切换。
这会产生少量开销,通常为1或2微秒。
I/O-bound vs Compute-bound(或CPU-Bound)
一个花费大部分时间等待某事发生的操作称为IO-bound
IO绑定操作通常涉及输入或输出,但这不是硬性要求:Thread.Sleep()也被视为IO-bound
相反,一个花费大部分时间执行CPU密集型工作的操作称为Compute-bound。
阻塞vs忙等待(自旋)
Blocking vs Spinning
lO-bound操作的工作方式有两种:
①在当前线程上同步的等待: Console.ReadLine(),Thread.Sleep(),Thread.Join()...
②异步的操作,在稍后操作完成时触发一个回调动作。
同步等待的I/O-bound操作将大部分时间花在阻塞线程上(干等待)。
它们也可以周期性的在一个循环里进行 "打转(自旋)"
如下图两个例子:
在忙等待和阻塞方面有一些细微差别:
首先,如果您希望条件很快得到满足(可能在几微秒之内),则短暂自旋可能会很有效,因为它避免了上下文切换的开销和延迟。.NET Framework提供了特殊的方法和类来提供帮助SpinLock和 SpinWait。
其次,阻塞也不是零成本。这是因为每个线程在生存期间会占用大约1 MB的内存,并会给CLR和操作系统带来持续的管理开销。
因此,在需要处理成百上千个并发操作的大量I/O-bound程序的上下文中,阻塞可能会很麻烦。
所以,此类程序需要使用基于回调的方法,在等待时完全撤消其线程。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构