Time Slicing Between Five Threads In C#

1.3k views Asked by At

Here's a description of what the program should do. The program should create a file and five threads to write in that file...

The first thread should write from 1 to 5 into that file. The second thread should write from 1 to 10. The third thread should write from 1 to 15. The fourth thread should write from 1 to 20. The fifth thread should write from 1 to 25.

Moreover, an algorithm should be implemented to make each thread print 2 numbers and stops. the next thread should print two numbers and stop. and so on until all the threads finish printing their numbers. Here's the code I've developed so far...

using System;
using System.IO;
using System.Threading;
using System.Collections;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public static class OSAssignment
    {
        // First Thread Tasks...
        static void FirstThreadTasks(StreamWriter WritingBuffer)
        {
            for (int i = 1; i <= 5; i++)
            {
                if (i % 2 == 0)
                {
                    Console.WriteLine("[Thread1] " + i);
                    Thread.Sleep(i);
                }

                else
                {
                    Console.WriteLine("[Thread1] " + i);
                }
            }
        }

        // Second Thread Tasks...
        static void SecondThreadTasks(StreamWriter WritingBuffer)
        {
            for (int i = 1; i <= 10; i++)
            {
                if (i % 2 == 0)
                {
                    if (i == 10)
                        Console.WriteLine("[Thread2] " + i);

                    else
                    {
                        Console.WriteLine("[Thread2] " + i);
                        Thread.Sleep(i);
                    }
                }

                else
                {
                    Console.WriteLine("[Thread2] " + i);
                }
            }
        }

        // Third Thread Tasks..
        static void ThirdThreadTasks(StreamWriter WritingBuffer)
        {
            for (int i = 1; i <= 15; i++)
            {
                if (i % 2 == 0)
                {
                    Console.WriteLine("[Thread3] " + i);
                    Thread.Sleep(i);
                }

                else
                {
                    Console.WriteLine("[Thread3] " + i);
                }
            }
        }

        // Fourth Thread Tasks...
        static void FourthThreadTasks(StreamWriter WritingBuffer)
        {
            for (int i = 1; i <= 20; i++)
            {
                if (i % 2 == 0)
                {
                    if (i == 20)
                        Console.WriteLine("[Thread4] " + i);
                    else
                    {
                        Console.WriteLine("[Thread4] " + i);
                        Thread.Sleep(i);
                    }
                }

                else
                {
                    Console.WriteLine("[Thread4] " + i);
                }

            }
        }

        // Fifth Thread Tasks...
        static void FifthThreadTasks(StreamWriter WritingBuffer)
        {
            for (int i = 1; i <= 25; i++)
            {
                if (i % 2 == 0)
                {
                    Console.WriteLine("[Thread5] " + i);
                    Thread.Sleep(i);
                }

                else
                {
                    Console.WriteLine("[Thread5] " + i);
                }

            }
        }

        // Main Function...
        static void Main(string[] args)
        {
            FileStream File = new FileStream("output.txt", FileMode.Create, FileAccess.Write, FileShare.Write);
            StreamWriter Writer = new StreamWriter(File);
            Thread T1 = new Thread(() => FirstThreadTasks(Writer));
            Thread T2 = new Thread(() => SecondThreadTasks(Writer));
            Thread T3 = new Thread(() => ThirdThreadTasks(Writer));
            Thread T4 = new Thread(() => FourthThreadTasks(Writer));
            Thread T5 = new Thread(() => FifthThreadTasks(Writer));
            Console.WriteLine("Initiating Jobs...");
            T1.Start();
            T2.Start();
            T3.Start();
            T4.Start();
            T5.Start();
            Writer.Flush();
            Writer.Close();
            File.Close();
        }
    }
}

Here's the problems I'm facing...

  1. I cannot figure out how to make the 5 threads write into the same file at the same time even with making FileShare.Write. So I simply decided to write to console for time being and to develop the algorithm and see how it behaves first in console.

  2. Each time I run the program, the output is slightly different from previous. It always happen that a thread prints only one of it's numbers in a specific iteration and continues to output the second number after another thread finishes its current iteration.

  3. I've a got a question that might be somehow offtrack. If I removed the Console.WriteLine("Initiating Jobs..."); from the main method, the algorithm won't behave like I mentioned in Point 2. I really can't figure out why.

3

There are 3 answers

3
NSFW On BEST ANSWER

Please see James' answer as well. He points out a critical bug that escaped my notice: you're closing the file before the writer threads have finished. Consider posting a new question to ask how to solve that problem, since this "question" is already three questions rolled into one.

  1. FileShare.Write tells the operating system to allow other attempts to open the file for writing. Typically this is used for systems that have multiple processes writing to the same file. In your case, you have a single process and it only opens the file once, so this flag really makes no difference. It's the wrong tool for the job.

To coordinate writes between multiple threads, you should use locking. Add a new static field to the class:

private static object synchronizer = new object();

Then wrap each write operation on the file with a lock on that object:

lock(synchronizer)
{
    Console.WriteLine("[Thread1] " + i);    
}

This wil make no difference while you're using the Console, but I think it will solve the problem you had with writing to the file.

Speaking of which, switching from file write to console write to sidestep the file problem was a clever idea, so kudos for that. Howver an even better implementation of that idea would be to replace all of the write calls with a call to a single function, e.g. "WriteOutput(string)" so that you can switch everything from file to console just by changing one line in that function.

And then you could put the lock into that function as well.

  1. Threaded stuff is not deterministic. It's guaranteed that each thread will run, but there are no guarantees about ordering, when threads will be interrupted, which thread will interrupt which, etc. It's a roll of the dice every time. You just have to get used to it, or go out of your way to force thing to happen in a certain sequence if that really matters for your application.

  2. I dunno about this one. Seems like that shouldn't matter.

0
James On
  1. Your main function is finishing and closing the file before the threads have started writing to it, so you can you use Thread.Join to wait for a thread to exit. Also I'd advise using a using statement for IDisposable objects.

  2. When you have a limited resources you want to share among threads, you'll need a locking mechanism. Thread scheduling is not deterministic. You've started 5 threads and at that point it's not guaranteed which one will run first. lock will force a thread to wait for a resource to become free. The order is still not determined so T3 might run before T2 unless you add additional logic/locking to force the order as well.

  3. I'm not seeing much difference in the behavior but free running threads will produce some very hard to find bugs especially relating to timing issues.

As an extra note I'd avoid using Sleep as a way of synchronizing threads.

To effectively get one thread to write at a time you need to block all other threads, there's a few methods for doing that such as lock, Mutex, Monitor,AutoResetEvent etc. I'd use an AutoResetEvent for this situation. The problem you then face is each thread needs to know which thread it's waiting for so that it can wait on the correct event.

0
Herc On

OK, I'm coming to this rather late, and but from a theoretical point of view, I/O from multiple threads to a specific end-point is inevitably fraught.

In the example above, it would almost certainly faster and safer to queue the output into an in-memory structure, each thread taking an exclusive lock before doing so, and then having a separate thread to output to the device.