欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 艺术 > C Sharp C#之多线程

C Sharp C#之多线程

2025/4/19 17:13:49 来源:https://blog.csdn.net/qq_44716013/article/details/147127322  浏览:    关键词:C Sharp C#之多线程

目录

一、线程的基本概念

二、线程的生命周期

三、线程的创建与启动

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调度的基本单位
  • 同一进程内的多个线程共享进程的资源(如内存、文件句柄等)
  • 线程拥有自己独立的执行栈和程序计数器 

二、线程的生命周期

  1. 创建(Created):线程对象被创建,但尚未开始执行。它处于就绪状态,等待被调度执行。

  2. 启动(Started):调用线程的Start方法后,线程进入启动状态。此时,线程堆栈被分配,但线程尚未开始执行。

  3. 运行(Running):线程被调度并开始执行。它正在使用CPU资源执行代码。

  4. 阻塞(Blocked):线程因为等待某个资源(如锁、文件I/O等)而无法继续执行。

  5. 等待(Waiting):线程正在等待某个事件或方法完成,如Thread.Sleep、Monitor.Wait等。

  6. 睡眠(Sleeping):线程因为调用了Thread.Sleep方法而暂时停止执行。

  7. 终止(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("线程池线程执行任务");
});

线程池特点:

  1. 自动管理线程数量
  2. 减少线程创建销毁开销
  3. 适合短时间任务
  4. 不适合长时间运行的任务

可以通过ThreadPool.SetMinThreads和SetMaxThreads调整线程池大小。

七、线程安全编程实践

确保线程安全的几种方法

  1. 使用线程安全集合:如ConcurrentBag、ConcurrentQueue等 
  2. 使用ThreadLocal:为每个线程提供独立的数据副本 
  3. 避免共享状态:尽可能减少共享数据
  4. 正确使用同步原语:合理选择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)

减少上下文切换的方法:

  1. 合理设置线程优先级
  2. 避免创建过多线程
  3. 减少锁竞争
  4. 合理使用async/await

总结

C#中的线程工作原理涉及操作系统层面的调度和.NET框架提供的管理机制。理解线程生命周期、同步机制、优先级调度等概念对编写高效、安全的多线程程序至关重要。现代C#开发中,虽然直接使用Thread类的场景减少,但理解其底层原理仍然对使用Task等高级抽象有帮助

实际开发中应根据需求选择合适的并发模型:对于IO密集型任务推荐使用async/await;CPU密集型任务可考虑Parallel类或Task;需要精细控制时再使用Thread。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词