lock与SemaphoreSlim的区别与应用
在多线程编程中,线程同步是确保数据一致性和避免竞争条件的重要手段。本文将深入探讨 lock(object)
和 SemaphoreSlim
这两种常用的同步机制,详细分析它们的区别、适用场景以及如何在实际开发中选择合适的同步工具。
一、lock(object)
(或 Monitor
)
1. 单线程访问: lock
关键字用于确保在同一时间只有一个线程能够访问被保护的代码块。它实现了互斥锁(mutex),适用于单线程临界区的保护。这意味着当一个线程进入锁定区域时,其他试图进入该区域的线程将被阻塞,直到锁被释放。
2. 锁范围: lock
保护的代码块只能在单个线程内运行,进入锁定区域的线程会被阻塞直到锁被释放。它提供了一种简单而有效的方式来防止多个线程同时访问共享资源。
3. 实现方式: lock
是基于 Monitor
类的,它是CLR提供的一种基础同步机制。Monitor
提供了 Enter
和 Exit
方法来显式地进入和离开临界区,lock
关键字在语法上对其进行了简化,使得代码更加易读。
4. 简单易用: lock
语法简洁,易于使用,适用于简单的线程同步需求。典型的用法如下:
private static readonly object _lockObject = new object(); public void SomeMethod() { lock (_lockObject) { // Critical section. } }
5. 不支持异步: lock
不能用于异步代码块,不能与 await
一起使用。这意味着在需要异步处理的场景中,lock
并不适用。
二、SemaphoreSlim
1. 多线程访问: SemaphoreSlim
允许指定同时可以访问资源的线程数。它可以用作计数信号量,允许多个线程并发访问指定数量的资源。例如,初始化为1的 SemaphoreSlim
等价于一个互斥锁,而初始化为大于1的 SemaphoreSlim
则允许指定数量的线程并发访问。
2. 锁范围: SemaphoreSlim
可以控制同时访问资源的多个线程,适用于需要限制并发访问数量的场景。它在资源访问控制方面提供了更大的灵活性。
3. 实现方式: SemaphoreSlim
是一个轻量级的、基于信号量的同步机制。它支持异步操作,使其在需要控制并发访问的异步编程中尤为适用。
4. 异步支持: SemaphoreSlim
提供了异步等待功能,可以与 async
和 await
关键字一起使用。这使得它非常适用于异步编程模型,能够有效避免异步方法中的阻塞问题。
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public async Task SomeMethodAsync() { await _semaphore.WaitAsync(); try { // Critical section. } finally { _semaphore.Release(); } }
5. 复杂使用场景: SemaphoreSlim
适用于更复杂的并发控制需求,如限制并发访问数量或需要异步支持的场景。它能够根据具体的并发需求,灵活调整允许并发访问的线程数量。
6.工作原理:
- WaitAsync 方法:
- 当调用
WaitAsync
方法时,如果信号量的计数大于 0,则计数会减 1,并且立即允许调用线程进入临界区。 - 如果信号量的计数为 0,则调用线程会进入等待状态,直到有其他线程调用
Release
方法增加了计数,或者超时。
- 当调用
- Release 方法:
- 调用
Release
方法会增加信号量的计数。如果有线程正在等待进入临界区,则会释放其中一个线程,使其可以进入临界区执行任务。 - 如果没有等待的线程,则信号量的计数会累加,超过
SemaphoreSlim(int initialCount, int maxCount)
其中的maxCount
则会抛出SemaphoreFullException
。
- 调用
三、实际应用:AsyncLoadHelper<TData>
在实际开发中,我们常常需要在异步方法中进行线程同步。下面是一个 AsyncLoadHelper<TData>
类的实现,它通过 SemaphoreSlim
确保数据加载操作的线程安全性,并且支持异步操作。
public class AsyncLoadHelper<TData> : BindableBase, IDisposable { private TData _data; private bool _isLoading; private Exception _loadingException; private readonly Lazy<DelegateCommand> _loadCommand; private readonly Func<CancellationToken, Task<TData>> _dataLoadMethod; private CancellationTokenSource _cts; private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1); public TData Data { get => _data; set => SetProperty(ref _data, value); } public bool IsLoading { get => _isLoading; set => SetProperty(ref _isLoading, value); } public Exception LoadingException { get => _loadingException; set => SetProperty(ref _loadingException, value); } public DelegateCommand LoadCommand => _loadCommand.Value; public AsyncLoadHelper(Func<CancellationToken, Task<TData>> dataLoadMethod) { _dataLoadMethod = dataLoadMethod; _loadCommand = new Lazy<DelegateCommand>(() => new DelegateCommand(async () => await ExecuteLoadDataAsync(), () => !IsLoading).ObservesProperty(() => IsLoading)); } public virtual async Task ExecuteLoadDataAsync() { if (IsLoading) return; await _asyncLock.WaitAsync(); _cts?.Cancel(); _cts = new CancellationTokenSource(); IsLoading = true; LoadingException = null; try { Data = await _dataLoadMethod(_cts.Token); } catch (OperationCanceledException) { // Handle if needed } catch (Exception e) { LoadingException = e; } finally { IsLoading = false; _asyncLock.Release(); } } public void Dispose() { _cts?.Cancel(); _cts?.Dispose(); _asyncLock.Dispose(); } }
关键点
-
防止重复加载:使用
IsLoading
防止在加载过程中多次调用ExecuteLoadDataAsync
。 -
同步机制:通过
await _asyncLock.WaitAsync()
确保在任何时候只有一个线程能够进入临界区。 -
释放信号量:无论加载操作是否成功,
finally
块中的_asyncLock.Release()
确保了信号量总是被释放,从而不会阻塞后续的加载请求。 -
简单同步:如果需要简单的、单线程的临界区保护,
lock
是更简单和直接的选择。它的语法简洁,易于理解和使用,非常适合基本的线程同步需求。 -
并发控制和异步支持:如果需要控制同时访问资源的线程数量,或者需要在异步代码中使用,
SemaphoreSlim
是更为合适的选择。它不仅支持异步操作,还能灵活地控制并发线程的数量,适用于更复杂的同步场景。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
2020-05-29 MvvmLight + Microsoft.Extensions.DependencyInjection + WpfApp(.NetCore3.1)
2018-05-29 pybind11简介