C
C#6mo ago
cheeze2000

need help understanding `Task`s

if my CPU has 6 cores and 12 threads, how many Tasks can run at once?
57 Replies
Angius
Angius6mo ago
Any number Tasks are not related to threads
cheeze2000
cheeze20006mo ago
i tried running 99 tasks at once, but they seem to be limited to 12, why is that so? i also have this code
public class Program
{
private static readonly Stopwatch stopwatch = new();
private static readonly Mutex mutex = new();
private static Queue<int> queue = new();

public static void Main()
{
var N = 72;

queue = new Queue<int>(Enumerable.Range(1, N));

stopwatch.Start();
var tasks = Enumerable.Range(1, 99).Select(_ => Task.Run(Process));
Task.WaitAll([.. tasks]);
Console.WriteLine($"Took {stopwatch.ElapsedMilliseconds / 1000.0:F2} s");
}

private static void Process()
{
mutex.WaitOne();

if (queue.Count > 0)
{
queue.Dequeue();
mutex.ReleaseMutex();
Thread.Sleep(1000);
Process();
}
else
{
mutex.ReleaseMutex();
return;
}
}
}
public class Program
{
private static readonly Stopwatch stopwatch = new();
private static readonly Mutex mutex = new();
private static Queue<int> queue = new();

public static void Main()
{
var N = 72;

queue = new Queue<int>(Enumerable.Range(1, N));

stopwatch.Start();
var tasks = Enumerable.Range(1, 99).Select(_ => Task.Run(Process));
Task.WaitAll([.. tasks]);
Console.WriteLine($"Took {stopwatch.ElapsedMilliseconds / 1000.0:F2} s");
}

private static void Process()
{
mutex.WaitOne();

if (queue.Count > 0)
{
queue.Dequeue();
mutex.ReleaseMutex();
Thread.Sleep(1000);
Process();
}
else
{
mutex.ReleaseMutex();
return;
}
}
}
the time taken is always different, between 5.0s, 5.5s and 6.0s. i'm not sure why
Saryn
Saryn6mo ago
Tasks are not exactly concurrent processes. You have a limited number of threads for processing a queue of tasks. They will process as threads become available.
Angius
Angius6mo ago
You're also using thread.Sleep() which blocks the thread Use await Task.Delay() instead, so that the thread gets freed for another task to execute
cheeze2000
cheeze20006mo ago
yea, but based on my understanding, if there are 12 threads processing the tasks, why can the code above run in less than 6 seconds?
Saryn
Saryn6mo ago
Because you're telling a thread to not work for a set amount of time with thread.Sleep() as the other person said.
cheeze2000
cheeze20006mo ago
but wouldn't that cause the time taken to be longer than 6 seconds? my concern is that it takes less than 6 seconds
canton7
canton76mo ago
I don't get why your Process is recursive
cheeze2000
cheeze20006mo ago
i want them to keep taking integers from the queue until it's depleted
canton7
canton76mo ago
A loop is the normal way, which doesn't risk blowing the stack
cheeze2000
cheeze20006mo ago
there are 72 integers in the queue, so there are essentially 72 things to do
Saryn
Saryn6mo ago
So you want 72 functions on the stack until the queue is empty?
cheeze2000
cheeze20006mo ago
i want to make 99 tasks that collectively dequeue until it's empty yes and measure the time but every dequeue waits 1 second
Saryn
Saryn6mo ago
also popping from a queue recursively is not using a queue correctly.
canton7
canton76mo ago
Anyway, Task.Run runs ona a ThreadPool thread. The ThreadPool has a variable number of threads, and if it runs out, it will start increasing the number of threads at a rate of a couple a second (IIRC)
cheeze2000
cheeze20006mo ago
this is not some sort of production code or best practices code, it's just what i randomly wrote to test out concurrency how would you explain why the code above runs in less than 6 seconds? if i run the code, it would say 5.50 s on my 6c/12t cpu, i just want to know how this works
canton7
canton76mo ago
The default is something like 4? So you'll start off with 4-ish tasks in parallel, then after 0.5s or so another couple will start, then 0.5s after that the first 4 finish and another 4 items start processing, and at the same time another couple of threads get added
Saryn
Saryn6mo ago
cpu thread != c# thread.
canton7
canton76mo ago
Start logging when each task start/stops, and what thread ID it's running on, that'll make it clearer
cheeze2000
cheeze20006mo ago
so a cpu thread can be instructed to do multiple c# threads?
canton7
canton76mo ago
No, C# threads map 1-1 to CPU threads
cheeze2000
cheeze20006mo ago
now i'm getting conflicting answers.
Saryn
Saryn6mo ago
That wouldn't make sense with your previous comment about increasing number of threads.
cheeze2000
cheeze20006mo ago
ok i will try that
canton7
canton76mo ago
Well, OS threads, not "CPU threads". There's no such thing as a "CPU thread" Well, unless you're getting into the realm of hyperthreading. But cpu cores/ virtual cores / etc aren't relevant here
Saryn
Saryn6mo ago
True, but just wanted to differentiate as he was wondering why a 6c/12t cpu was not being reflected given his test code
cheeze2000
cheeze20006mo ago
oh wow, here are the logs
canton7
canton76mo ago
Log the starts, and timestamps as well
cheeze2000
cheeze20006mo ago
initially only threads 1-12, but after some time there are threads 13-16
canton7
canton76mo ago
Yeah, as I say, the thread pool will start adding threads after you exhaust all the threads it does have
cheeze2000
cheeze20006mo ago
with timestamps
canton7
canton76mo ago
Add milliseconds to that. You should see jumps as a loads of threads finish at the same time, and jumps as new threads are added
cheeze2000
cheeze20006mo ago
so if 4 threads are added, meaning there are now 16 threads, how many of these threads are actually executing in parallel? 12?
canton7
canton76mo ago
There's no reason you won't be using all of the available threads in the threadpool at the same time
cheeze2000
cheeze20006mo ago
but there's a limitation to how many tasks my cpu can take at an instant right are you saying all the threads in the threadpool execute at the same time? then my question is how is this possible
canton7
canton76mo ago
A thread that's sleeping won't be running on a CPU
cheeze2000
cheeze20006mo ago
omg
canton7
canton76mo ago
Thread.Sleep just means "Start running again in X seconds"
cheeze2000
cheeze20006mo ago
okay that's the answer to my question so if i have 16 threads, none of them is sleeping (doing some intensive task), how many would be executing at the same time? actually i'm gonna do that experiment and see what happens
Saryn
Saryn6mo ago
As many as your cpu allows for.
cheeze2000
cheeze20006mo ago
that would be my first guess, let me try it
Saryn
Saryn6mo ago
Just mind it maybe not be 12 if you have a 6c/12t CPU... Your computer is doing other things that isn't just running your program.
cap5lut
cap5lut6mo ago
thats actually handled at OS level, has to do with context switches and a lot other different complex stuff
canton7
canton76mo ago
Well. They'll all be "executing" at the same time, but what happens in reality is that the OS gives each thread a time-slice, then puts it to sleep and moves onto the next one. Your CPU hardware determines how many threads can actually be executing in parallel at the same time
cheeze2000
cheeze20006mo ago
i'm trying to simulate an intensive task that isn't sleeping, i'm currently using for (int i = 0; i < int.MaxValue; i++) {} seems like only 12 threads are spawned at most
canton7
canton76mo ago
The threadpool should keep spawning new ones, IIRC
cap5lut
cap5lut6mo ago
the starting size of the thread pool is at least hardware dependent and might be OS dependent as well, for windows i have seen that its usually logical processors, as in ur hardware threads
cheeze2000
cheeze20006mo ago
you're right, i ran the code for longer and now threads 13, 14 start showing up i see, that makes sense from the experiments that i've done
cap5lut
cap5lut6mo ago
there are some different factors that can bring the thread pool to more than that. one is for example tasks that are created TaskCreationOptions.LongRunning, for this often its own thread is created, that may or may not die later
canton7
canton76mo ago
TaskCreationOptions.LongRunning doesn't touch the threadpool at all It just does new Thread()
cap5lut
cap5lut6mo ago
i thought i have read somewhere that that thread can later be added to the thread pool as well, might be misremembering
cap5lut
cap5lut6mo ago
indeed, sorry for the misinformation.
cap5lut
cap5lut6mo ago
but in the end its finishing that early because its spawning new threads and thats why u finish in such a short time, like canton7 already said logical processor count at that, so each hardware thread counts towards this also, 1 second already counts being a long running task, iirc
cap5lut
cap5lut6mo ago
if u just want to parallelize cpu bound work, u would use other means than popping a lot of tasks: https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/data-parallelism-task-parallel-library
Data Parallelism (Task Parallel Library) - .NET
Read how the Task Parallel Library (TPL) supports data parallelism to do the same operation concurrently on a source collection or array's elements in .NET.
cheeze2000
cheeze20006mo ago
sure thing, i'll keep that in mind but the reason why i'm playing with concurrency is because i'm presenting about it so i thought of using c# as an example