【发布时间】:2020-02-01 17:54:38
【问题描述】:
编辑:我更新了我的示例以使用 https://github.com/StephenCleary/AsyncEx 库。仍在等待可用的提示。
有一些资源,由字符串标识(例如文件、URL 等)。我正在寻找资源的锁定机制。我找到了 2 种不同的解决方案,但每种都有其问题:
第一个是使用ConcurrentDictionary类和AsyncLock:
using Nito.AsyncEx;
using System.Collections.Concurrent;
internal static class Locking {
private static ConcurrentDictionary<string, AsyncLock> mutexes
= new ConcurrentDictionary<string, AsyncLock>();
internal static AsyncLock GetMutex(string resourceLocator) {
return mutexes.GetOrAdd(
resourceLocator,
key => new AsyncLock()
);
}
}
异步使用:
using (await Locking.GetMutex("resource_string").LockAsync()) {
...
}
同步使用:
using (Locking.GetMutex("resource_string").Lock()) {
...
}
这很安全,但问题是字典越来越大,当没有人等待锁定时,我没有看到一种线程安全的方法来从字典中删除项目。 (我也想避免全局锁。)
我的第二个解决方案将字符串散列为0 和N - 1 之间的数字,并锁定这些:
using Nito.AsyncEx;
using System.Collections.Concurrent;
internal static class Locking {
private const UInt32 BUCKET_COUNT = 4096;
private static ConcurrentDictionary<UInt32, AsyncLock> mutexes
= new ConcurrentDictionary<UInt32, AsyncLock>();
private static UInt32 HashStringToInt(string text) {
return ((UInt32)text.GetHashCode()) % BUCKET_COUNT;
}
internal static AsyncLock GetMutex(string resourceLocator) {
return mutexes.GetOrAdd(
HashStringToInt(resourceLocator),
key => new AsyncLock()
);
}
}
如您所见,第二种解决方案仅降低了冲突的概率,但并没有避免它们。我最大的担心是它会导致死锁:避免死锁的主要策略是始终以特定顺序锁定项目。但是使用这种方法,不同的项目可以以不同的顺序映射到相同的桶,例如:(A->X,B->Y),(C->Y,D->X)。因此,使用此解决方案无法安全地锁定多个资源。
有没有更好的解决方案? (我也欢迎对上述两种解决方案提出批评。)
【问题讨论】:
-
Lock/UnlockAPI 看起来有点笨拙且容易出错。您不喜欢利用方便的lock语句的API 吗?例如:lock (mutex.Get("some_string")) {/*protected region*/} -
@TheodorZoulias 谢谢,我在想这个。优点是,如果出现异常,锁会自动删除。但作为下一步,我将使用 NuGet 库中的 AsyncAutoResetEvent 将其扩展到异步情况。而且我看不到使用
lock语句实现的类似东西。 -
@TheodorZoulias 虽然我可以使用这个:github.com/StephenCleary/AsyncEx#asynclock 所以我将进入
lock声明方向,谢谢。 -
实现基于
SemaphoreSlim的异步一次性储物柜非常简单,但另一方面,使用 Stephen Cleary 经过良好测试的库不会出错!
标签: c# multithreading asynchronous .net-core locking