多线程的最佳实践

还是那句话,多线程很有用,但并非那么好玩。请使用之前确认你真的掌握了它们

 

本文请参考:http://msdn.microsoft.com/zh-cn/library/1c9txz50.aspx

有关重点摘录如下

  • 不要使用 Thread..::.Abort 终止其他线程。对另一个线程调用 Abort 无异于引发该线程的异常,也不知道该线程已处理到哪个位置。

  • 不要使用 Thread..::.SuspendThread..::.Resume 同步多个线程的活动。请使用 MutexManualResetEventAutoResetEventMonitor

  • 不要从主程序中控制辅助线程的执行(如使用事件),而应在设计程序时让辅助线程负责等待任务,执行任务,并在完成时通知程序的其他部分。如果辅助线程不阻止,请考虑使用线程池线程。Monitor..::.PulseAll 在辅助线程阻止的情况下很有用。

  • 不要将类型用作锁定对象。例如,避免在 C# 中使用 lock(typeof(X)) 代码,或在 Visual Basic 中使用 SyncLock(GetType(X)) 代码,或将 Monitor..::.EnterType 对象一起使用。对于给定类型,每个应用程序域只有一个 System..::.Type 实例。如果您锁定的对象的类型是 public,您的代码之外的代码也可锁定它,但会导致死锁。有关其他信息,请参见可靠性最佳做法

  • 锁定实例时要谨慎,例如,C# 中的 lock(this) 或 Visual Basic 中的 SyncLock(Me)。如果您的应用程序中不属于该类型的其他代码锁定了该对象,则会发生死锁。

  • 一定要确保已进入监视器的线程始终离开该监视器,即使当线程在监视器中时发生异常也是如此。C# 的 lock 语句和 Visual Basic 的 SyncLock 语句可自动提供此行为,它们用一个 finally 块来确保调用 Monitor..::.Exit。如果无法确保调用 Exit,请考虑将您的设计更改为使用 Mutex。Mutex 在当前拥有它的线程终止后会自动释放。

  • 一定要针对那些需要不同资源的任务使用多线程,避免向单个资源指定多个线程。例如,任何涉及 I/O 的任务都会从其拥有其自己的线程这一点得到好处,因为此线程在 I/O 操作期间将阻止,从而允许其他线程执行。用户输入是另一种可从专用线程获益的资源。在单处理器计算机上,涉及大量计算的任务可与用户输入和涉及 I/O 的任务并存,但多个计算量大的任务将相互竞争。

  • 对于简单的状态更改,请考虑使用 Interlocked 类的方法,而不是 lock 语句(在 Visual Basic 中为 SyncLock)。lock 语句是一个优秀的通用工具,但是 Interlocked 类为必须是原子性的更新提供了更好的性能。如果没有争夺,它会在内部执行一个锁定前缀。

C#中进行多线程编程时,遵循最佳实践可以有效提升程序的性能与稳定性。以下是一些关键的最佳实践,涵盖线程管理、同步机制以及资源访问控制等方面。 ### 3.1 使用高级抽象(Task 和 async/await) C# 提供了 `Task` 类和 `async/await` 模式来简化并发编程。相比直接使用 `Thread` 类,这些高级抽象能够更好地利用线程池资源,并且更容易编写异步代码[^3]。 ```csharp Task<int> resultTask = Task.Run(() => { // 执行耗时操作 return SomeExpensiveOperation(); }); int result = await resultTask; ``` 这种方式不仅提高了代码可读性,还减少了手动管理线程的工作量。 ### 3.2 避免共享状态 当多个线程需要访问相同的数据结构或对象时,应该尽量避免共享可变状态。如果必须共享,则应确保该状态是不可变的或者通过适当的同步机制加以保护[^4]。 ```csharp private readonly object lockObject = new object(); private int sharedCounter = 0; public void IncrementCounter() { lock (lockObject) { sharedCounter++; } } ``` 使用 `lock` 语句块确保同一时间只有一个线程能够修改 `sharedCounter` 变量,从而防止数据竞争问题。 ### 3.3 正确选择同步原语 根据具体场景选择合适的同步机制非常重要。例如: - **互斥锁(Mutex)**:适用于跨进程的同步需求。 - **信号量(Semaphore)**:用于限制同时访问的线程数量。 - **事件(EventWaitHandle)**:允许一个或多个线程等待某个条件成立后再继续执行。 - **ReaderWriterLockSlim**:允许多个读取者同时访问资源,但写入时独占访问权。 每种同步机制都有其适用范围,合理选用可以提高程序效率并减少死锁风险。 ### 3.4 异常处理与取消操作 在多线程环境中正确地处理异常至关重要。可以通过 `try-catch` 块捕获任务中的异常,并利用 `CancellationToken` 实现优雅的任务取消功能。 ```csharp CancellationTokenSource cts = new CancellationTokenSource(); try { await Task.Run(async () => { while (!cts.Token.IsCancellationRequested) { // 执行循环工作 await Task.Delay(100); } }, cts.Token); } catch (OperationCanceledException ex) { Console.WriteLine("任务被取消:" + ex.Message); } ``` 此外,在长时间运行的任务中加入对取消令牌的支持有助于实现更灵活的任务控制逻辑。 ### 3.5 资源泄漏预防 确保所有开启的线程最终都能被妥善关闭,释放相关联的所有系统资源。对于不再使用的对象应及时调用 `Dispose()` 方法以避免内存泄露。 ```csharp using (ManualResetEvent mre = new ManualResetEvent(false)) { // 使用 mre 进行同步... } // 离开 using 块后自动调用 Dispose() ``` 使用 `using` 语句可以帮助自动管理实现了 `IDisposable` 接口的对象生命周期。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值