I am running on my Mac 2 similar code samples, one in C++, another in C#. 2 simple tasks that execute in parallel (or at least I want them to), one printing '+' in a loop, the other printing '-' in a loop. I was expecting the output from the 2 samples to be quite similar, but they are quite a bit different to my surprise.
C++ seems to truly run the tasks in parallel. I can see +- alternating nicely on every run, but C# seems to run one task for a while, then switch to the other task and run that for a while. Something like this:
C++: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C# : ++++++++++---------++++++------
I understand assumptions cannot be made about how parallel threads are run, it's just curious to me that C++ produces such a nice result, consistently.
Thanks for your time!
C#:
using System;
using System.Threading.Tasks;
public class ConcurrentTasks
{
public static void Main(String[] args)
{
var task1 = Task.Run(()=>DemoTask("+"));
var task2 = Task.Run(()=>DemoTask("-"));
var res1 = task1.Result;
var res2 = task2.Result;
Console.WriteLine("\nResults:");
Console.WriteLine(res1);
Console.WriteLine(res2);
}
private static String DemoTask(String label)
{
for (int i = 0; i < 1000; i++)
{
Console.Write(label);
}
return label + " result";
}
}
// mcs ConcurrentTasks.cs
// mono ConcurrentTasks.exe
C++:
#include <iostream>
#include <sstream>
#include <future>
using namespace std;
string demoTask(char label)
{
for (int i = 0; i < 1000; i++)
{
cout << label;
}
stringstream ss;
ss << label;
ss << " result";
return ss.str();
}
int main()
{
auto task1 = async(demoTask, '+');
auto task2 = async(demoTask, '-');
auto res1 = task1.get();
auto res2 = task2.get();
cout << endl << "Results:" << endl;
cout << res1 << endl;
cout << res2 << endl;
return 0;
}
// g++ --std=c++14 -Wall ConcurrentTasks.cpp -o ConcurrentTasks.exe
// ./ConcurrentTasks.exe
Edit: I've changed the C# sample to use bare Thread and the result is the same.
C# with Thread:
using System;
using System.Threading;
public class ConcurrentTasks
{
public static void Main(String[] args)
{
var t1 = new Thread(() => DemoTask("+"));
var t2 = new Thread(() => DemoTask("-"));
t1.Start();
t2.Start();
}
private static String DemoTask(String label)
{
for (int i = 0; i < 1000; i++)
{
Console.Write(label);
}
return label + " result";
}
}
Granularity could be one reason. TPL may have coarse granularity which scheduling the tasks, but C++ async
implementation might have fine granularity. This essentially means C++ would be taking more time, since just for processing +
or -
it is scheduling another task in between.
Another reason could be how cout
and Console
have implemented the output streams. How the locks might be taken to render something on screen? Any buffering being implemented? You see, Console.Write
may be buffering, and then printing after a while, but cout
may just be printing it immediately.
Hence, you should do something else, rather than depending on underlying I/O of the language - may be put the character on a shared static-sized array with shared int
as index to shared array (they are atomic, don't need locks Lock it with lightest synchronization primitive like reader-writer lock). No, don't use vector
or Array
- because in that case you are again dependent on something you don't know!
Use Release build in both cases, with best-possible same optimization options. Also, ensure you are running them on same machine.
So it looks like C#'s console had the biggest impact.
I've modified the C# sample yet again, to not use the console while executing the 2 tasks. Instead I use a shared output char array. The results are dramatically different, very close to C++, although not as perfect and consistent.
It's amazing how the C++ program produces perfect +- pairs on every run, even with the cout in use. The C# output is each time slightly different and often I still see one thread finishing before the next starts. I had to increase significantly the number of iterations because with a small number of iterations the tasks ran in sequence always.
Here's the updated C# sample (note the interlocked increment!):
using System;
using System.Threading;
using System.Threading.Tasks;
public class ConcurrentTasks
{
private static char[] m_out;
private static int m_index = -1;
public static void Main(String[] args)
{
var iterations = 5000;
m_out = new char[iterations * 2];
var task1 = Task.Run(()=>DemoTask(iterations, '+'));
var task2 = Task.Run(()=>DemoTask(iterations, '-'));
var res1 = task1.Result;
var res2 = task2.Result;
for (int i = 0; i < m_out.Length; i++)
{
Console.Write(m_out[i]);
}
Console.WriteLine("\nResults:");
Console.WriteLine(res1);
Console.WriteLine(res2);
}
private static String DemoTask(int iterations, char label)
{
for (int i = 0; i < iterations; i++)
{
int index = Interlocked.Increment(ref m_index);
m_out[index] = label;
}
return label + " result";
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With