xavier
xavier

Reputation: 41

Trying to understand multi-threading in C#

I'm trying to understand the basics of multi-threading so I built a little program that raised a few question and I'll be thankful for any help :)

Here is the little program:

class Program
{
    public static int count;
    public static int max;
    static void Main(string[] args)
    {
        int t = 0;
        DateTime Result;
        Console.WriteLine("Enter Max Number : ");
        max = int.Parse(Console.ReadLine());
        Console.WriteLine("Enter Thread Number : ");
        t = int.Parse(Console.ReadLine());

        count = 0;

        Result = DateTime.Now;
        List<Thread> MyThreads = new List<Thread>();
        for (int i = 1; i < 31; i++)
        {
            Thread Temp = new Thread(print);
            Temp.Name = i.ToString();
            MyThreads.Add(Temp);
        }

        foreach (Thread th in MyThreads)
            th.Start();

        while (count < max)
        {
        }

        Console.WriteLine("Finish , Took : " + (DateTime.Now - Result).ToString() + " With : " + t + " Threads.");
        Console.ReadLine();
    }

    public static void print()
    {
        while (count < max)
        {
            Console.WriteLine(Thread.CurrentThread.Name + " - " + count.ToString());
            count++;
        }
    }
}

I checked this with some test runs:

I made the maximum number 100, and it seems to be that the fastest execution time is with 2 threads which is 80% faster than the time with 10 threads.

Questions:

1) Threads 4-10 don't print even one time, how can it be?

2) Shouldn't more threads be faster?

I made the maximum number 10000 and disabled printing.

With this configuration, 5 threads seems to be fastest.

Why there is a change compared to the first check?

And also in this configuration (with printing) all the threads print a few times. Why is that different from the first run where only a few threads printed?

Is there is a way to make all the threads print one by one? In a line or something like that?

Thank you very much for your help :)

Upvotes: 4

Views: 630

Answers (3)

user7116
user7116

Reputation: 64068

Your code is certainly a first step into the world of threading, and you've just experienced the first (of many) headaches!

To start with, static may enable you to share a variable among the threads, but it does not do so in a thread safe manner. This means your count < max expression and count++ are not guaranteed to be up to date or an effective guard between threads. Look at the output of your program when max is only 10 (t set to 4, on my 8 processor workstation):

T0 - 0
T0 - 1
T0 - 2
T0 - 3
T1 - 0 // wait T1 got count = 0 too!
T2 - 1 // and T2 got count = 1 too!
T2 - 6
T2 - 7
T2 - 8
T2 - 9
T0 - 4
T3 - 1 // and T3 got count = 1 too!
T1 - 5

To your question about each thread printing one-by-one, I assume you're trying to coordinate access to count. You can accomplish this with synchronization primitives (such as the lock statement in C#). Here is a naive modification to your code which will ensure only max increments occur:

static object countLock = new object();

public static void printWithLock()
{
    // loop forever
    while(true)
    {
        // protect access to count using a static object
        // now only 1 thread can use 'count' at a time
        lock (countLock)
        {
            if (count >= max) return;

            Console.WriteLine(Thread.CurrentThread.Name + " - " + count.ToString());
            count++;
        }
    }
}

This simple modification makes your program logically correct, but also slow. The sample now exhibits a new problem: lock contention. Every thread is now vying for access to countLock. We've made our program thread safe, but without any benefits of parallelism!

Threading and parallelism is not particularly easy to get right, but thankfully recent versions of .Net come with the Task Parallel Library (TPL) and Parallel LINQ (PLINQ).

The beauty of the library is how easy it would be to convert your current code:

var sw = new Stopwatch();

sw.Start();
Enumerable.Range(0, max)
          .AsParallel()
          .ForAll(number =>
               Console.WriteLine("T{0}: {1}",
                                 Thread.CurrentThread.ManagedThreadId,
                                 number));

Console.WriteLine("{0} ms elapsed", sw.ElapsedMilliseconds);

// Sample output from max = 10
// 
// T9: 3
// T9: 4
// T9: 5
// T9: 6
// T9: 7
// T9: 8
// T9: 9
// T8: 1
// T7: 2
// T1: 0
// 30 ms elapsed

The output above is an interesting illustration of why threading produces "unexpected results" for newer users. When threads execute in parallel, they may complete chunks of code at different points in time or one thread may be faster than another. You never really know with threading!

Upvotes: 7

Microfed
Microfed

Reputation: 2890

Look carefuly:

    t = int.Parse(Console.ReadLine());

    count = 0;

    Result = DateTime.Now;
    List<Thread> MyThreads = new List<Thread>();
    for (int i = 1; i < 31; i++)
    {
        Thread Temp = new Thread(print);
        Temp.Name = i.ToString();
        MyThreads.Add(Temp);
    }

I think you missed a variable t ( i < 31).

You should read many books on parallel and multithreaded programming before writing code, because programming language is just a tool. Good luck!

Upvotes: 0

Skod
Skod

Reputation: 155

Your print function is far from thread safe, that's why 4-10 doesn't print. All threads share the same max and count variables.

Reason for the why more threads slows you down is likely the state change taking place each time the processor changes focus between each thread.

Also, when you're creating a lot of threads, the system needs to allocate new ones. Most of the time it is now advisable to use Tasks instead, as they are pulled from a system managed thread-pool. And thus doesn't necessarily have to be allocated. The creation of a distinct new thread is rather expensive.

Take a look here anyhow: http://msdn.microsoft.com/en-us/library/aa645740(VS.71).aspx

Upvotes: 0

Related Questions