1

I am spawning 10 threads using a for loop in C# as shown below

internal class Program
{
    static Mutex mutex = new Mutex();
    static void Main(string[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            Thread t1 = new Thread(AcquireMutex);
            t1.Name = string.Format("Thread{0}", i + 1);
            t1.Start();
        }
        
    }

    private static void AcquireMutex(object? obj)
    {
        mutex.WaitOne();
        DoSomething();
        mutex.ReleaseMutex();
        Console.WriteLine("Mutex released by {0}", Thread.CurrentThread.Name);
    }

    private static void DoSomething()
    {
        Thread.Sleep(1000);
        Console.WriteLine("Mutex acquired by {0}", Thread.CurrentThread.Name);
    }
}

As per my understanding, in the first iteration of the for loop Thread 1 starts and so on. But while running the above code,I came to see that Thread 10 acquires the mutex lock before Thread 1 in some cases.

So my query is , if the for loop spawns Thread 1 first ,then Thread 10, why does Thread 10 acquires the lock first in some cases?

  • 3
    In my understanding: Main starts first thread but doesn't wait for its core code to be executed before starting the second and so on; so it could happen that threads are not run sequentially as you would. By the way: if you need sequential code why would you use threads? :D – Marco Aug 04 '23 at 05:52
  • 4
    It doesn't matter which thread acquires the mutex first. – ProgrammingLlama Aug 04 '23 at 05:56
  • @Marco I am a bit new to multithreading in c#, so just experimenting by creating multiple threads in different ways. I have not used a for loop for multithreading use cases, but a new work requirement came to utilize it. Thanks for the info. Do let me know of any good links to explore more on this. – Subin Suthan Aug 04 '23 at 06:08
  • 1
    in this code, it is completely random who aquires the mutex first, the fact that thread 1 gets it first most of the time is coincidence, because it is started as first, but really what you have here is a classical race condition. by the way, if you want things like this done in c# .. you really should use default pattens that are even made available to you in the framework. look at the task parallel library(TPL). in this case. Parallel.For (https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-write-a-simple-parallel-for-loop) – Michael Schönbauer Aug 04 '23 at 06:13
  • @MichaelSchönbauer Thanks for the inputs, will surely delve into it. – Subin Suthan Aug 04 '23 at 06:27
  • 4
    I would really recommend finding some better sources to learn from. Both `Thread` and `Mutex` are mostly used for highly specialized scenarios. Modern code typically uses things like `lock`, `Task.Run`, `async/await`, `Parallel.For` etc. Since that simplifies the code and help ensure that it is safe. There should be plenty of articles on multi threading in general, and for c# specifically, if you look around a bit. – JonasH Aug 04 '23 at 08:23

1 Answers1

4

So my query is, if the for loop spawns Thread 1 first, then Thread 10, why does Thread 10 acquires the lock first in some cases?

Welcome to the world of multithreading. The reason we start multiple threads is because we want things to run in parallel, and in parallelism the concept of order does not apply. Things are running concurrently, not the one after the other. The work is delegated to the operating system, that assigns physical CPU cores to software threads according to its own heuristics, without much consideration about what these threads are actually doing. The cores are assigned to threads in time-slices, so that the number of threads that can make progress concurrently is much larger than the number of physical CPU cores. A time-slice might end at any arbitrary point. For example a thread can be suspended by the OS in the middle of a x = x + 1 operation (which is translated to multiple CPU instructions), and resumed 50 milliseconds later overwriting any modifications on x that other threads might have done in the meantime.

If you need to restore the order in the chaos, and run specific parts of your multithreaded code not only sequentially (not concurrently) but also in a predefined order, there is no built-in synchronization primitive that can help you achieve this goal. There are two relevant primitives, the CountdownEvent and the Barrier, but they are not doing exactly what you want. You might find a solution in this question: Is there a synchronization class that guarantee FIFO order in C#?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • That's some great insights :)). One question, if a thread has acquired a lock and is processing some operation that takes time, but is suspended by the OS since its time slice has ended. So what exactly does "Thread Suspending" means in this context. Like is it killed or anything like that? – Subin Suthan Aug 04 '23 at 09:45
  • 1
    @SubinSuthan by "Thread Suspending" I don't mean the [`Thread.Suspend`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.suspend) method. I mean the period during which a thread is idle, because the CPU is busy with running other threads. A thread is not aware of the fact that its execution is suspended and resumed continuously by the OS. The only way to get an indication of this happening, is to use a `Stopwatch` and measure the duration between the one command and the next ([demo](https://dotnetfiddle.net/3kWlV6 "ThreadRunningSleepingSlices by T.Zoulias")). – Theodor Zoulias Aug 04 '23 at 10:03
  • @SubinSuthan to be honest I don't think that I have answered your question completely satisfactory. The 10 threads in your example are doing so little work that the time-slicing mechanism of the OS shouldn't come into play. Maybe someone could offer a better/deeper explanation. What I really wanted to say is that, although I don't know the exact reason for the out-of-order behavior, as a developer experienced in multithreading I have been trained to expect chaotic behavior from threads, and to rely heavily on careful synchronization in order for my programs to function correctly. – Theodor Zoulias Aug 04 '23 at 12:20
  • Yea, I totally agree as there is no heavy workload to utilize multiple threads. Its just a simple code snippet to get started with multithreading. As per the discussion here, I can conclude that (**correct me if I am wrong**) the order of execution of the 10 threads is determined by the thread scheduler of the OS, which we do not have control. – Subin Suthan Aug 04 '23 at 13:16
  • @SubinSuthan yes, that's my assumption too. I believe that the source of the chaos is deeper than the .NET layer, it goes down to the bowels of the OS kernel. But I don't have deep and detailed knowledge about how the various operating systems are implemented, so don't quote me on that! – Theodor Zoulias Aug 04 '23 at 13:28