什么时候应该在 .NET 中使用 SemaphoreSlim?
When should I use SemaphoreSlim in .NET?
我有一个从 Redis 读取散列的方法:
private Task FetchHashesFromRedis(List<string> redisKeys, ConcurrentBag<LiveDataModel> liveDataModels,
CancellationToken cancellationToken)
{
var parallelism = Environment.ProcessorCount;
var semafore = new SemaphoreSlim(initialCount: parallelism, maxCount: parallelism);
var tasks = new List<Task>();
Parallel.ForEach(redisKeys, (key) =>
{
tasks.Add(ExecuteOne(key, semafore, liveDataModels, cancellationToken));
});
return Task.WhenAll(tasks);
}
redisKeys
列表计数始终为 1000,因此它始终会发出一千个请求。
FetchHashesFromRedis
方法总是一样的
ExecuteOne
第一种情况下的方法如下所示:
private async Task ExecuteOne(string redisKey, SemaphoreSlim semafore, ConcurrentBag<LiveDataModel> liveDataModels,
CancellationToken cancellationToken)
{
var liveData = await _getLiveDataFromRedis.ExecuteAsync(redisKey, cancellationToken);
if (liveData != null)
{
liveDataModels.Add(liveData);
}
}
在第一种情况下,向 Redis 发出 1000 个请求需要 1.5 秒,而我对得到的模型所做的所有工作都是如此。
ExecuteOne
第二种情况(使用信号量)中的方法如下所示:
private async Task ExecuteOne(string redisKey, SemaphoreSlim semafore, ConcurrentBag<LiveDataModel> liveDataModels,
CancellationToken cancellationToken)
{
await semafore.WaitAsync(cancellationToken);
try
{
var liveData = await _getLiveDataFromRedis.ExecuteAsync(redisKey, cancellationToken);
if (liveData != null)
{
liveDataModels.Add(liveData);
}
}
finally
{
semafore.Release();
}
}
在第二种情况下,向 Redis 发出 1000 个请求需要 4.5 秒 以及我对得到的模型所做的所有辅助工作。 (与第一种情况相同的请求量)
所以第一种情况和第二种情况之间的唯一区别是在第二种情况下我使用这个:
await semafore.WaitAsync(cancellationToken);
在 finally
块中我使用:
semafore.Release();
为什么当我使用 semafore
时需要更多时间(最多 3 倍)?在这种情况下我应该使用 semafore 还是不使用?我什么时候应该使用信号灯?
注意:_getLiveDataFromRedis.ExecuteAsync(redisKey, cancellationToken);
方法不是线程安全的,它只是从 redis 和 returns LiveDataModel
读取不同的值
Why when I use semafore it takes more time (up to 3 times more)?
可能是因为它限制了并发IO操作的数量。使用信号量会将并发调用的数量限制为处理器的数量,但是,由于它涉及 IO,因此大部分时间只是等待,不需要处理器时间。因此,将并发限制为核心数量毫无意义。尝试增加 maxCount 并查看是否有助于提高性能。
Should I use semafore in this case or no?
因为它比较慢,而且似乎不需要任何线程安全原因,答案可能是否定的。
And when should I use semafore?
我很少使用信号量。我知道最有说服力的原因是我是否需要异步锁,即最大计数为 1 的信号量。它有专门的用例,但在大多数情况下,我发现处理同步的更高级别的原语更有用。
我可能建议阅读 DataFlow,这可能允许您设置更适合您的 use-case 的异步处理管道。
我有一个从 Redis 读取散列的方法:
private Task FetchHashesFromRedis(List<string> redisKeys, ConcurrentBag<LiveDataModel> liveDataModels,
CancellationToken cancellationToken)
{
var parallelism = Environment.ProcessorCount;
var semafore = new SemaphoreSlim(initialCount: parallelism, maxCount: parallelism);
var tasks = new List<Task>();
Parallel.ForEach(redisKeys, (key) =>
{
tasks.Add(ExecuteOne(key, semafore, liveDataModels, cancellationToken));
});
return Task.WhenAll(tasks);
}
redisKeys
列表计数始终为 1000,因此它始终会发出一千个请求。
FetchHashesFromRedis
方法总是一样的
ExecuteOne
第一种情况下的方法如下所示:
private async Task ExecuteOne(string redisKey, SemaphoreSlim semafore, ConcurrentBag<LiveDataModel> liveDataModels,
CancellationToken cancellationToken)
{
var liveData = await _getLiveDataFromRedis.ExecuteAsync(redisKey, cancellationToken);
if (liveData != null)
{
liveDataModels.Add(liveData);
}
}
在第一种情况下,向 Redis 发出 1000 个请求需要 1.5 秒,而我对得到的模型所做的所有工作都是如此。
ExecuteOne
第二种情况(使用信号量)中的方法如下所示:
private async Task ExecuteOne(string redisKey, SemaphoreSlim semafore, ConcurrentBag<LiveDataModel> liveDataModels,
CancellationToken cancellationToken)
{
await semafore.WaitAsync(cancellationToken);
try
{
var liveData = await _getLiveDataFromRedis.ExecuteAsync(redisKey, cancellationToken);
if (liveData != null)
{
liveDataModels.Add(liveData);
}
}
finally
{
semafore.Release();
}
}
在第二种情况下,向 Redis 发出 1000 个请求需要 4.5 秒 以及我对得到的模型所做的所有辅助工作。 (与第一种情况相同的请求量)
所以第一种情况和第二种情况之间的唯一区别是在第二种情况下我使用这个:
await semafore.WaitAsync(cancellationToken);
在 finally
块中我使用:
semafore.Release();
为什么当我使用 semafore
时需要更多时间(最多 3 倍)?在这种情况下我应该使用 semafore 还是不使用?我什么时候应该使用信号灯?
注意:_getLiveDataFromRedis.ExecuteAsync(redisKey, cancellationToken);
方法不是线程安全的,它只是从 redis 和 returns LiveDataModel
Why when I use semafore it takes more time (up to 3 times more)?
可能是因为它限制了并发IO操作的数量。使用信号量会将并发调用的数量限制为处理器的数量,但是,由于它涉及 IO,因此大部分时间只是等待,不需要处理器时间。因此,将并发限制为核心数量毫无意义。尝试增加 maxCount 并查看是否有助于提高性能。
Should I use semafore in this case or no?
因为它比较慢,而且似乎不需要任何线程安全原因,答案可能是否定的。
And when should I use semafore?
我很少使用信号量。我知道最有说服力的原因是我是否需要异步锁,即最大计数为 1 的信号量。它有专门的用例,但在大多数情况下,我发现处理同步的更高级别的原语更有用。
我可能建议阅读 DataFlow,这可能允许您设置更适合您的 use-case 的异步处理管道。