目录
一、线程的基本概念
二、线程的生命周期
三、线程的创建与启动
1.线程可以被命名
2.单线程(可以理解为同步)和多线程(可以理解为异步)演示
四、线程调度与优先级
五、线程同步机制
1. lock关键字(Monitor类)
2. Mutex(互斥体)
3. Semaphore(信号量)
4. 事件(EventWaitHandle)
六、线程池(ThreadPool)
七、线程安全编程实践
八、线程中断与终止
1. 中断(Interrupt)
2. 终止(Abort)
九、上下文切换
总结
一、线程的基本概念
在 C# 中,System.Threading.Thread 类用于线程的工作。它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程。当 C# 程序开始执行时,主线程是自动创建的。我们可以使用Thread类创建多线程。
线程(Thread)是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。线程有时被称为轻量级进程(Lightweight Process),是进程的一个实体,是CPU调度和分派的基本单位。
线程与进程的主要区别在于:
- 进程是资源分配的基本单位,线程是CPU调度的基本单位
- 同一进程内的多个线程共享进程的资源(如内存、文件句柄等)
- 线程拥有自己独立的执行栈和程序计数器
二、线程的生命周期
-
创建(Created):线程对象被创建,但尚未开始执行。它处于就绪状态,等待被调度执行。
-
启动(Started):调用线程的Start方法后,线程进入启动状态。此时,线程堆栈被分配,但线程尚未开始执行。
-
运行(Running):线程被调度并开始执行。它正在使用CPU资源执行代码。
-
阻塞(Blocked):线程因为等待某个资源(如锁、文件I/O等)而无法继续执行。
-
等待(Waiting):线程正在等待某个事件或方法完成,如Thread.Sleep、Monitor.Wait等。
-
睡眠(Sleeping):线程因为调用了Thread.Sleep方法而暂时停止执行。
-
终止(Terminated):线程的执行完成,或者因为异常而被强行终止。一旦线程终止,它就不能再次被启动。
三、线程的创建与启动
1.线程可以被命名
//Thread是创建和控制线程的类
Thread th = Thread.CurrentThread;
th.Name = "MainThread";
Console.WriteLine(th.Name);
2.单线程(可以理解为同步)和多线程(可以理解为异步)演示
static DateTime date1;
static DateTime date2;
static DateTime date3;
public static void test1()
{//单线程 只有当上面的任务完成时才会执行下面的任务//线程占用过程中不可以做任何操作date1 = DateTime.Now;Thread.Sleep(3000);Console.WriteLine("荤菜炒好了");date2 = DateTime.Now;Thread.Sleep(1000);Console.WriteLine("素菜炒好了");date3 = DateTime.Now;Console.WriteLine((date2 - date1).ToString("ss"));Console.WriteLine((date3 - date2).ToString("ss"));Console.WriteLine((date3 - date1).ToString("ss"));
}
结果是这样的
荤菜被炒好,用了3s,在荤菜炒完之后素菜才开始炒,用了1s,总共用了4s。
我们再来看使用了多线程(两个线程),一个主线程和一个子线程
public static void test2()
{//在t1这个线程中调用HC()这个方法Thread t1 = new Thread(HC);t1.Start();Thread.Sleep(1000);Console.WriteLine("素菜炒好了");date3 = DateTime.Now;
}
public static void HC()
{date1 = DateTime.Now;Thread.Sleep(3000);Console.WriteLine("荤菜炒好了");date2 = DateTime.Now;Console.WriteLine((date2 - date1).ToString("ss"));Console.WriteLine((date2 - date3).ToString("ss"));
}
结果:
这里date2-date3只间隔了两秒,说明在运行程序的时候主线程并没有等待t1这个子线程,而是直接略过了他继续往后执行,所以明明先炒的荤菜,但是素菜先炒完。
四、线程调度与优先级
线程调度由操作系统负责,基于线程优先级决定哪个线程获得CPU时间。在C#中,可以通过Thread.Priority属性设置线程优先级,它接受ThreadPriority枚举值:
- Lowest (最低)
- BelowNormal (低于正常)
- Normal (默认)
- AboveNormal (高于正常)
- Highest (最高)
Thread thread2 = new Thread(priorityTest.ThreadMethod);
thread2.Name = "ThreadTwo";
thread2.Priority = ThreadPriority.BelowNormal; // 设置线程优先级
操作系统采用抢占式调度,高优先级线程会抢占低优先级线程的执行,但过度使用高优先级可能导致其他线程“饥饿”应谨慎使用。
五、线程同步机制
多线程访问共享资源时需要进行同步,避免竞态条件(Race Condition)和数据不一致。C#提供了多种同步机制:
1. lock关键字(Monitor类)
private object lockObj = new object();void SafeMethod()
{lock(lockObj){// 临界区代码}
}
lock实际使用的是Monitor.Enter和Monitor.Exit。它确保同一时刻只有一个线程能进入临界区
2. Mutex(互斥体)
Mutex类似于lock,但可以跨进程使用
Mutex mutex = new Mutex();
mutex.WaitOne(); // 获取锁
try
{// 临界区代码
}
finally
{mutex.ReleaseMutex(); // 释放锁
}
3. Semaphore(信号量)
SemaphoreSlim semaphore = new SemaphoreSlim(3); // 允许3个线程同时访问
await semaphore.WaitAsync();
try
{// 受保护的代码
}
finally
{semaphore.Release();
}
4. 事件(EventWaitHandle)
用于线程间通信,ManualResetEvent和AutoResetEvent是最常用的两种
ManualResetEventSlim signal = new ManualResetEventSlim(false);// 线程1
signal.Wait(); // 等待信号// 线程2
signal.Set(); // 发送信号
六、线程池(ThreadPool)
频繁创建销毁线程开销大,所以线程池提供了线程重用机制
ThreadPool.QueueUserWorkItem(state =>
{Console.WriteLine("线程池线程执行任务");
});
线程池特点:
- 自动管理线程数量
- 减少线程创建销毁开销
- 适合短时间任务
- 不适合长时间运行的任务
可以通过ThreadPool.SetMinThreads和SetMaxThreads调整线程池大小。
七、线程安全编程实践
确保线程安全的几种方法
- 使用线程安全集合:如ConcurrentBag、ConcurrentQueue等
- 使用ThreadLocal:为每个线程提供独立的数据副本
- 避免共享状态:尽可能减少共享数据
- 正确使用同步原语:合理选择lock、Mutex等
// 使用线程安全集合
ConcurrentBag<int> bag = new ConcurrentBag<int>();
Parallel.For(0, 100, i => bag.Add(i));// 使用ThreadLocal
ThreadLocal<string> threadName = new ThreadLocal<string>(() =>
{return "Thread" + Thread.CurrentThread.ManagedThreadId;
});
八、线程中断与终止
1. 中断(Interrupt)
中断处于WaitSleepJoin状态的线程
thread.Interrupt(); // 抛出ThreadInterruptedException
2. 终止(Abort)
强制终止线程(已过时,不推荐使用)
thread.Abort(); // 抛出ThreadAbortException
现代C#推荐使用CancellationToken实现协作式取消
九、上下文切换
当CPU从一个线程切换到另一个线程时,会发生上下文切换,涉及保存和恢复线程状态。过多的上下文切换会导致性能下降,称为"抖动"(Thrashing)
减少上下文切换的方法:
- 合理设置线程优先级
- 避免创建过多线程
- 减少锁竞争
- 合理使用async/await
总结
C#中的线程工作原理涉及操作系统层面的调度和.NET框架提供的管理机制。理解线程生命周期、同步机制、优先级调度等概念对编写高效、安全的多线程程序至关重要。现代C#开发中,虽然直接使用Thread类的场景减少,但理解其底层原理仍然对使用Task等高级抽象有帮助
实际开发中应根据需求选择合适的并发模型:对于IO密集型任务推荐使用async/await;CPU密集型任务可考虑Parallel类或Task;需要精细控制时再使用Thread。