In streams
I would not explicitly create threads myself.
It is much more preferable to use ThreadPool.QueueUserWorkItem
, or if you can use .Net 4.0, you will get a much more powerful Task parallel library , which also allows you to use ThreadPool threads in a much more powerful way ( Task.Factory.StartNew
worth a look)
What if we decide to move on to direct thread creation?
Suppose your list is [0] .Count returns 1000 items. Suppose also that you are doing this on a high-end (as of this writing) 16core machine. The immediate effect is that we have 1000 threads competing for these limited resources (16 cores).
The larger the number of tasks and the more each of them is completed, the more time will be spent switching contexts . In addition, creating threads is expensive, these overheads that create each thread can clearly be avoided if you take the approach of reusing existing threads.
Thus, although the initial intention of multithreading may be to increase the speed, as we can see, this can have the exact opposite effect.
How do we overcome "excessive" threads?
It uses ThreadPool
.
A thread pool is a collection of threads that can be used to perform a number of tasks in the background.
How do they work:
Once the thread in the pool completes its task, it returns to the waiting thread queue, where it can be reused. This reuse allows applications to avoid the cost of creating a new thread for each task.
Thread pools usually have the maximum number of threads. If all threads are busy, additional tasks are queued until they can be serviced as threads become available.
Thus, we see that using thread pool threads we are more efficient as
- in terms of maximizing actual work . Since we do not overload processors with threads, less time is spent switching between threads and more time actually executing the code that the thread should execute.
- Faster start of the stream : each stream stream is easily accessible, and does not wait until a new stream is created.
- in terms of minimizing memory consumption , threadpool will limit the number of threads to the size of threadpool, depending on any requests that go beyond the threadpool thread limit. (see
ThreadPool.GetMaxThreads
). The main reason for this design choice, of course, is that we do not oversaturate a limited number of cores with too many thread requests, while keeping the context moving to lower levels.
Too many Theories, let's test this whole theory!
That's right, itβs good to know all this in theory, but let's practice and see what the numbers tell us, with a simplified rough version of the application that can give us a rough indication of the difference in orders of magnitude. We will make a comparison between the new Thread, ThreadPool and the parallel task library (TPL)
new topic
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(); }
Result:

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(); }
Result:

Parallel Task Library (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(); }
Result:

So we can see that:
+--------+------------+------------+--------+ | | new Thread | ThreadPool | TPL | +--------+------------+------------+--------+ | Time | 6749 | 228ms | 222ms | | Memory | β300kb | β103kb | β123kb | +--------+------------+------------+--------+
The above goes well with what we expected in theory. High memory for the new topic, as well as lower overall performance compared to ThreadPool. ThreadPool and TPL have equivalent performance, with TPL having a slightly higher amount of memory than a pure thread pool, but it may be worth the cost for additional flexibility. Tasks provide (for example, cancellation, waiting for the completion of a request to complete a task).
At this point, we proved that using ThreadPool threads is the preferred option in terms of speed and memory.
However, we did not answer your question. How to track the status of running threads.
To answer your question
Given the ideas we got, here's how I approach him:
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
As a last note, you cannot use the variable i in your Thread.Start, since it will create a variable closure that will be used effectively for all threads. To get around this (assuming you need to access i), just create a copy of the variable and pass it in, it will make one short circuit to the thread, which will make it thread safe.
Good luck