В потоках
Я бы не стал явно создавать потоки самостоятельно.
Гораздо предпочтительнее использовать ThreadPool.QueueUserWorkItem
или, если вы действительно можете использовать .Net 4.0, вы получите гораздо более мощную параллельную библиотеку задач , которая также позволяетиспользовать потоки ThreadPool гораздо более мощным способом (Task.Factory.StartNew
стоит посмотреть)
Что, если мы решим использовать явное создание потоков?
Предположим, что ваш список [0] .Count возвращает 1000 элементов.Предположим также, что вы выполняете это на высокопроизводительной (на момент написания этой статьи) 16-ядерной машине.Непосредственный эффект состоит в том, что у нас есть 1000 потоков , конкурирующих за эти ограниченные ресурсы (16 ядер).
Чем больше количество задач и чем дольше выполняется каждая из них, тем больше времени будет потрачено на переключение контекста .Кроме того, создание потоков обходится дорого, поэтому можно избежать издержек на создание каждого потока явно, если использовать подход повторного использования существующих потоков.
Таким образом, в то время как первоначальное намерение многопоточности может быть увеличить скорость, как мы можем видеть, он может иметь противоположный эффект.
Как мы преодолеваем «чрезмерную» многопоточность?
Здесь вступает в игру ThreadPool
.
Пул потоков - это набор потоков, которые можно использовать для выполнения ряда задач в фоновом режиме.
Как они работают:
Как только поток в пуле завершает свою задачу, он возвращается в очередь ожидающих потоков, где его можно использовать повторно.Такое повторное использование позволяет приложениям избежать затрат на создание нового потока для каждой задачи.
Пулы потоков обычно имеют максимальное количество потоков.Если все потоки заняты, дополнительные задачи помещаются в очередь до тех пор, пока они не будут обслуживаться, когда потоки станут доступными.
Таким образом, мы можем видеть, что при использовании потоков пула потоков мы более эффективны как
- с точки зрения максимизации фактической работы Выполнение.Поскольку мы не слишком насыщаем процессоры потоками, меньше времени тратится на переключение между потоками и больше времени на выполнение кода, который должен делать поток.
- Ускоренный запуск потока : Каждый поток пула доступен в отличие от ожидания создания нового потока.
- с точки зрения минимизации потребления памяти , пул потоков будет ограничивать количество потоков размером пула потоков, ставя в очередь любые запросы, превышающие ограничение размера пула потоков.(см.
ThreadPool.GetMaxThreads
).Основная причина такого выбора дизайна, разумеется, заключается в том, что мы не перенасыщаем ограниченное количество ядер слишком большим количеством запросов потоков, сохраняя переключение контекста на более низкие уровни.
Слишком много теории, давайте проверим всю эту теорию!
Правильно, приятно знать все это в теории , но давайте рассмотрим еепопрактикуйтесь и посмотрите, что говорят нам цифры, с упрощенной грубой версией приложения, которая может дать нам грубое указание на разницу в порядках.Мы проведем сравнение между новым потоком, ThreadPool и параллельной библиотекой задач (TPL)
новый поток
static void Main(string[] args)
{
int itemCount = 1000;
Stopwatch stopwatch = new Stopwatch();
long initialMemoryFootPrint = GC.GetTotalMemory(true);
stopwatch.Start();
for (int i = 0; i < itemCount; i++)
{
int iCopy = i; // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
Thread thread = new Thread(() =>
{
// lets simulate something that takes a while
int k = 0;
while (true)
{
if (k++ > 100000)
break;
}
if ((iCopy + 1) % 200 == 0) // By the way, what does your sendMessage(list[0]['1']); mean? what is this '1'? if it is i you are not thread safe.
Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
});
thread.Name = "SID" + iCopy; // you can also use i here.
thread.Start();
}
Console.ReadKey();
Console.WriteLine(GC.GetTotalMemory(false) - initialMemoryFootPrint);
Console.ReadKey();
}
Результат:
ThreadPool.EnqueueUserWorkItem
static void Main(string[] args)
{
int itemCount = 1000;
Stopwatch stopwatch = new Stopwatch();
long initialMemoryFootPrint = GC.GetTotalMemory(true);
stopwatch.Start();
for (int i = 0; i < itemCount; i++)
{
int iCopy = i; // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
ThreadPool.QueueUserWorkItem((w) =>
{
// lets simulate something that takes a while
int k = 0;
while (true)
{
if (k++ > 100000)
break;
}
if ((iCopy + 1) % 200 == 0)
Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
});
}
Console.ReadKey();
Console.WriteLine("Memory usage: " + (GC.GetTotalMemory(false) - initialMemoryFootPrint));
Console.ReadKey();
}
Результат:
Параллельная библиотека задач (TPL)
static void Main(string[] args)
{
int itemCount = 1000;
Stopwatch stopwatch = new Stopwatch();
long initialMemoryFootPrint = GC.GetTotalMemory(true);
stopwatch.Start();
for (int i = 0; i < itemCount; i++)
{
int iCopy = i; // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
Task.Factory.StartNew(() =>
{
// lets simulate something that takes a while
int k = 0;
while (true)
{
if (k++ > 100000)
break;
}
if ((iCopy + 1) % 200 == 0) // By the way, what does your sendMessage(list[0]['1']); mean? what is this '1'? if it is i you are not thread safe.
Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
});
}
Console.ReadKey();
Console.WriteLine("Memory usage: " + (GC.GetTotalMemory(false) - initialMemoryFootPrint));
Console.ReadKey();
}
Результат:
Итак, мы можем видеть, что:
+--------+------------+------------+--------+
| | new Thread | ThreadPool | TPL |
+--------+------------+------------+--------+
| Time | 6749 | 228ms | 222ms |
| Memory | ≈300kb | ≈103kb | ≈123kb |
+--------+------------+------------+--------+
Вышесказанное прекрасно согласуется с тем, что мы ожидали в теории.Большой объем памяти для новых потоков, а также более низкая общая производительность по сравнению с ThreadPool.ThreadPool и TPL имеют эквивалентную производительность с TPL, имеющим немного больший объем памяти, чем чистый пул потоков, но это, вероятно, стоит заплатить, учитывая дополнительную гибкость, которую обеспечивают Задачи (например, отмена, ожидание завершения запроса статуса задачи)
На данный момент мы доказали, что использование потоков ThreadPool является предпочтительным вариантом с точки зрения скорости и памяти.
Тем не менее, мы не ответили на ваш вопрос.Как отследить состояние запущенных потоков.
Чтобы ответить на ваш вопрос
Учитывая полученные нами идеи, я подхожу к этому так:
List<string>[] list = listdbConnect.Select()
int itemCount = list[0].Count;
Task[] tasks = new Task[itemCount];
stopwatch.Start();
for (int i = 0; i < itemCount; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
{
// NOTE: Do not use i in here as it is not thread safe to do so!
sendMessage(list[0]['1']);
//calling callback function
});
}
// if required you can wait for all tasks to complete
Task.WaitAll(tasks);
// or for any task you can check its state with properties such as:
tasks[1].IsCanceled
tasks[1].IsCompleted
tasks[1].IsFaulted
tasks[1].Status
В качестве последнего замечания вы можете не используйте переменную i в вашем Thread.Start, так как это создаст замыкание по изменяющейся переменной, которая будет эффективно использоваться всеми потоками.Чтобы обойти это (предполагая, что вам нужен доступ к i), просто создайте копию переменной и передайте копию, это сделало бы одно замыкание на поток, что сделало бы его потокобезопасным.
Удачи!