У меня есть метод, который читает хэши из 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);
}
}
В этом первом случае выполнение 1000 запросов к Redis требует 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();
}
}
Во втором случае выполнение 1000 запросов к Redis требует 4,5 секунды со всей дополнительной работой, которую я выполняю с моделями, которые я получаю. (то же количество запросов, что и в первом случае)
Итак, единственная разница между первым и вторым случаем, что во втором случае я использую это:
await semafore.WaitAsync(cancellationToken);
и в блоке finally
я использую:
semafore.Release();
Почему при использовании semafore
требуется больше времени (до 3 раз)? Должен ли я использовать semafore в этом случае или нет? И когда я должен использовать семафор?
ПРИМЕЧАНИЕ. Метод _getLiveDataFromRedis.ExecuteAsync(redisKey, cancellationToken);
не является потокобезопасным, он просто считывает разные значения из Redis и возвращает LiveDataModel
@ Selvin нет, это не так, он просто читает разные значения из redis и возвращает LiveDataModel
это semaphore
, а не semafore
.
под потокобезопасностью я имею в виду, если его можно вызывать несколько раз одновременно ... во втором ExecuteOne
единственная причина использовать semafore - это если ExecuteAsync
нельзя вызывать несколько раз одновременно ... ConcurrentBag
позаботьтесь о добавлении элементов в в то же время в liveDataModels
@JHBonarius не имеет значения, что некоторые языки используют «нормальный» f
вместо «ненормальный» ph
Язык переполнения стека @Selvin - английский, как прямо указано в рекомендациях. Так что "ф" - это норма. (кроме того, термин составлен из древнегреческого φόρος, так что «фи» -> «ф»)
Я использую семафор в случае, когда я потенциально хочу вызывать внешний API тысячи раз, и этот API начнет дросселировать, когда будет получено более 100 параллельных запросов. Я запускаю первые 100 параллельных вызовов, и как только вызов возвращается, я отправляю следующий запрос. Могут быть и другие способы обработки такой логики, но использование семафора было для меня относительно простым.
Why when I use semafore it takes more time (up to 3 times more)?
Вероятно, потому что это ограничивает количество одновременных операций ввода-вывода. Использование семафора ограничит количество одновременных вызовов количеством процессоров, однако, поскольку он включает ввод-вывод, большую часть времени будет просто ожидание, процессорное время не требуется. Таким образом, ограничение параллелизма количеством ядер не имеет большого смысла. Попробуйте увеличить maxCount и посмотрите, поможет ли это производительности.
Should I use semafore in this case or no?
Поскольку это медленнее и, похоже, не требуется по какой-либо причине безопасности потоков, ответ, вероятно, будет «Нет».
And when should I use semafore?
Я редко использую семафоры. Самая веская причина, которую я знаю, это если мне нужна асинхронная блокировка, то есть семафор с maxcount 1. Для него есть специальные варианты использования, но в большинстве случаев я нахожу примитивы более высокого уровня, которые заботятся о синхронизации, более полезными.
Я мог бы предложить прочитать Поток данных, который может позволить вам настроить конвейер асинхронной обработки, который лучше подходит для вашего варианта использования.
это зависит от того, является ли
getLiveDataFromRedis.ExecuteAsync
потокобезопасным или нет