У меня есть служба WCF .NET 4.5 Single Instance, которая поддерживает набор элементов в списке, которые будут иметь одновременные одновременные читатели и писатели, но с гораздо большим количеством читателей, чем писатели.ConcurrentBag vs Custom Thread Safe List
я в настоящее время решение о том, чтобы использовать BCL ConcurrentBag<T>
или использовать свой собственный общий ThreadSafeList класс (который простирается IList<T>
и инкапсулирует BCL ReaderWriterLockSlim
как это больше подходит для нескольких одновременных читателей).
Я обнаружил многочисленные различия в производительности при тестировании этих реализаций путем моделирования параллельного сценария чтения 1 м (просто запуск запроса Sum Linq) и всего 100 авторов (добавление элементов в список).
Для моего теста производительности У меня есть список задач:
List<Task> tasks = new List<Task>();
Тест 1: Если я создаю 1е задачи читателя следует 100 писательских задачи, используя следующий код:
tasks.AddRange(Enumerable.Range(0, 1000000).Select(n => new Task(() => { temp.Where(t => t < 1000).Sum(); })).ToArray());
tasks.AddRange(Enumerable.Range(0, 100).Select(n => new Task(() => { temp.Add(n); })).ToArray());
Я получаю следующие результаты синхронизации:
- ConcurrentBag: ~ 300мс
- ThreadSafeList: ~ 520ms
Тест 2: Тем не менее, если я создаю 1е задачи читателя смешан с 100 задачами писателя (когда список задач, которые должны быть выполнено может быть {читатель, читатель, писатель, читатель, читатель, писатель и т.д.}
foreach (var item in Enumerable.Range(0, 1000000))
{
tasks.Add(new Task(() => temp.Where(t => t < 1000).Sum()));
if (item % 10000 == 0)
tasks.Add(new Task(() => temp.Add(item)));
}
я получить следующие результаты синхронизации:
- ConcurrentBag: ~ 4000ms
- ThreadSafeList: ~ 800ms
Мой код для получения времени выполнения для каждого теста выглядит следующим образом:
Stopwatch watch = new Stopwatch();
watch.Start();
tasks.ForEach(task => task.Start());
Task.WaitAll(tasks.ToArray());
watch.Stop();
Console.WriteLine("Time: {0}ms", watch.Elapsed.TotalMilliseconds);
Эффективность ConcurrentBag в тесте 1 лучше и эффективность ConcurrentBag в тесте 2 хуже, чем мой пользовательской реализации, поэтому я считаю это трудным решением, по которому можно использовать.
Q1. Почему результаты отличаются друг от друга, когда единственное, что я меняю, - это упорядочение задач в списке?
Q2. Есть ли лучший способ изменить мой тест, чтобы сделать его более справедливым?
ConcurrentBag оптимизирован для одной и той же темы, которая написана в сумке, которая считывается из сумки. Если у вас нет того же самого потока, который читается из переключателя мешка, вместо ConcurrentQueue. Прочитайте [эту страницу MSDN] (https://msdn.microsoft.com/en-us/library/dd997373 (v = vs.110) .aspx), в ваших примерах вы используете «Чистый сценарий производителя-потребителя», каждая нить либо читает, либо только записывает в сумку, ни одна нить не читает и не записывает в тот же поток. –
Я согласен - это приводит меня к тому, чтобы спросить, когда вы когда-либо используете ConcurrentBag, потому что из моего понимания вы не можете одновременно читать и писать в одном и том же потоке ........ можете ли вы? Поэтому у каждого потока будет свой собственный список. Наверное, называть Сумку частью параллельных коллекций было плохой идеей из-за ее неэффективности в многопоточном сценарии? .... если нет чего-то, чего я не вижу! – user978139
Сумка предназначена для ситуаций, когда у вас есть куча рабочих потоков, которые все дампы работают в пул, а затем по завершении выходят из того же пула. Если работник заканчивает всю работу, которую он вложил (способ, в котором он хранит данные внутри, он вернет данные, в которые вставлен поток), он начнет «красть» работу из других потоков. –