ScholarQuill logoScholarQuillUniversity Notes
  • Notes
  • Past Papers
  • Blogs
  • Todo
Login
ScholarQuill logoScholarQuillUniversity Notes
Login
NotesPast PapersBlogsTodo
More
SubjectsDiscussionCGPA CalculatorGPA CalculatorStudent PortalCourse Outline
About
About usPrivacy PolicyReportContact
Notes
Past Papers
Blogs
Todo
Analytics
    Current Subject
    🧩
    Advanced Programming
    CSI-415
    Progress0 / 55 topics
    Topics
    1. Visual Programming Basics2. Introduction to Events3. Fundamentals of Event-Driven Programming4. Message Handling5. User Interfaces6. Graphics Device Interface7. Painting and Drawing8. Windows Management9. Input Devices10. Resources11. String and Menu Resource12. Dialogs and Windows Controls13. Common Controls14. Dynamic Link Libraries (DLLs)15. Threads and Synchronization16. Network Programming17. Building Class Libraries at the Command Line18. Class Libraries19. Using References20. Assemblies21. Private Assembly Deployment22. Shared Assembly Deployment23. Configuration Overview24. Configuration Files25. Programmatic Access to Configuration26. Using SDK Tools for Signing and Deployment27. Metadata28. Reflection29. Late Binding30. Directories and Files31. Serialization32. Attributes33. Memory Management and Garbage Collection34. Threading and Synchronization35. Asynchronous Delegates36. Application Domains37. Marshal by Value38. Marshal by Reference39. Authentication and Authorization40. Configuring Security41. Code Access Security42. Code Groups43. Evidence44. Permissions45. Role-Based Security46. Principals and Identities47. Using Data Readers48. Using Data Sets49. Interacting with XML Data50. Tracing Event Logs51. Using the Boolean Switch and Trace Switch Classes52. Print Debugging Information with the Debug Class53. Instrumenting Release Builds with the Trace Class54. Using Listeners55. Implementing Custom Listeners
    CSI-415›Threads and Synchronization
    Advanced ProgrammingTopic 15 of 55

    Threads and Synchronization

    7 minread
    1,231words
    Intermediatelevel

    Threads and Synchronization in C#

    In C#, threads and synchronization are crucial concepts when dealing with concurrent execution. Multithreading allows an application to perform multiple operations simultaneously, improving performance and responsiveness. Synchronization ensures that shared resources are accessed in a thread-safe manner, preventing issues like data corruption, race conditions, and deadlocks.

    Below is a detailed explanation of threads and synchronization in C#.


    1. Threads in C#

    A thread is the smallest unit of execution within a process. It represents a single path of execution in a program. In modern applications, multiple threads can run concurrently, enabling multitasking and more efficient use of CPU resources.

    Creating and Starting Threads

    In C#, threads are created and managed using the System.Threading namespace. The Thread class allows you to create and manipulate threads.

    Basic Thread Creation and Execution

    using System;
    using System.Threading;
    
    class Program
    {
        static void Main()
        {
            // Create a new thread and pass the method to be executed
            Thread thread = new Thread(DoWork);
            
            // Start the thread
            thread.Start();
    
            // Wait for the thread to finish
            thread.Join();
        }
    
        // Method to be executed by the thread
        static void DoWork()
        {
            Console.WriteLine("Thread is working...");
            Thread.Sleep(2000); // Simulate work by sleeping for 2 seconds
            Console.WriteLine("Thread work is done!");
        }
    }
    
    • Thread.Start(): Starts the execution of the thread.
    • Thread.Join(): Makes the main thread wait for the other thread to finish.

    Thread Pool

    A ThreadPool provides a collection of worker threads that can be used for short-lived tasks. Instead of creating a new thread each time, you can use the thread pool to reuse existing threads.

    using System;
    using System.Threading;
    
    class Program
    {
        static void Main()
        {
            // Queue a work item to the thread pool
            ThreadPool.QueueUserWorkItem(DoWork);
        }
    
        static void DoWork(object state)
        {
            Console.WriteLine("Thread pool worker is working...");
        }
    }
    

    The ThreadPool is more efficient when managing many short-lived tasks compared to creating individual threads, as it avoids the overhead of creating and destroying threads.


    2. Synchronization in C#

    When multiple threads access shared resources simultaneously, it can lead to problems like race conditions, data corruption, and unpredictable behavior. Synchronization is used to ensure that only one thread accesses shared resources at a time, preventing these issues.

    Thread Synchronization Techniques

    1. Locking with lock Statement

    The lock keyword in C# is used to ensure that only one thread can execute a block of code at a time. It works by obtaining a mutex (mutual exclusion) on an object.

    using System;
    using System.Threading;
    
    class Program
    {
        private static int sharedResource = 0;
        private static object lockObject = new object();
    
        static void Main()
        {
            Thread thread1 = new Thread(Increment);
            Thread thread2 = new Thread(Increment);
    
            thread1.Start();
            thread2.Start();
    
            thread1.Join();
            thread2.Join();
    
            Console.WriteLine($"Shared resource value: {sharedResource}");
        }
    
        static void Increment()
        {
            lock (lockObject) // Only one thread can enter this block at a time
            {
                int temp = sharedResource;
                temp++;
                Thread.Sleep(100); // Simulate some work
                sharedResource = temp;
            }
        }
    }
    
    • The lock keyword ensures that the code inside the block is only executed by one thread at a time.
    • The lockObject is an object that is used to synchronize access. You can use any object, but it is common to use a dedicated locking object.

    2. Monitor Class

    The Monitor class provides more fine-grained control over locking and synchronization. It allows for acquiring and releasing locks, as well as waiting for conditions.

    using System;
    using System.Threading;
    
    class Program
    {
        private static int sharedResource = 0;
        private static object lockObject = new object();
    
        static void Main()
        {
            Thread thread1 = new Thread(Increment);
            Thread thread2 = new Thread(Increment);
    
            thread1.Start();
            thread2.Start();
    
            thread1.Join();
            thread2.Join();
    
            Console.WriteLine($"Shared resource value: {sharedResource}");
        }
    
        static void Increment()
        {
            Monitor.Enter(lockObject); // Acquire lock
    
            try
            {
                int temp = sharedResource;
                temp++;
                Thread.Sleep(100); // Simulate some work
                sharedResource = temp;
            }
            finally
            {
                Monitor.Exit(lockObject); // Release lock
            }
        }
    }
    
    • Monitor.Enter(lockObject): Acquires the lock.
    • Monitor.Exit(lockObject): Releases the lock.
    • The try-finally block ensures that the lock is always released, even if an exception occurs.

    3. Mutex Class

    A Mutex is used to synchronize threads across processes. It can be used to control access to a resource across different applications, not just within the same process.

    using System;
    using System.Threading;
    
    class Program
    {
        static Mutex mutex = new Mutex();
    
        static void Main()
        {
            Thread thread1 = new Thread(DoWork);
            Thread thread2 = new Thread(DoWork);
    
            thread1.Start();
            thread2.Start();
    
            thread1.Join();
            thread2.Join();
        }
    
        static void DoWork()
        {
            mutex.WaitOne(); // Acquire the mutex
            try
            {
                Console.WriteLine("Working...");
                Thread.Sleep(1000); // Simulate work
            }
            finally
            {
                mutex.ReleaseMutex(); // Release the mutex
            }
        }
    }
    
    • mutex.WaitOne(): Acquires the mutex lock.
    • mutex.ReleaseMutex(): Releases the mutex lock.

    A Mutex is useful when you need synchronization not only within your application but also across different applications or processes.

    4. Semaphore Class

    A Semaphore is used to limit the number of threads that can access a resource concurrently. It allows for more flexibility than a Mutex or lock.

    using System;
    using System.Threading;
    
    class Program
    {
        static Semaphore semaphore = new Semaphore(2, 2); // Allow only 2 threads at a time
    
        static void Main()
        {
            Thread thread1 = new Thread(DoWork);
            Thread thread2 = new Thread(DoWork);
            Thread thread3 = new Thread(DoWork);
    
            thread1.Start();
            thread2.Start();
            thread3.Start();
    
            thread1.Join();
            thread2.Join();
            thread3.Join();
        }
    
        static void DoWork()
        {
            semaphore.WaitOne(); // Acquire the semaphore
            try
            {
                Console.WriteLine("Working...");
                Thread.Sleep(1000); // Simulate work
            }
            finally
            {
                semaphore.Release(); // Release the semaphore
            }
        }
    }
    
    • semaphore.WaitOne(): Acquires the semaphore.
    • semaphore.Release(): Releases the semaphore.

    This allows a maximum of 2 threads to run concurrently in this example, which helps limit the load on a shared resource.


    3. Deadlocks and Starvation

    Deadlocks

    A deadlock occurs when two or more threads are waiting for each other to release resources, causing all the threads involved to be stuck indefinitely. Deadlocks usually happen when two threads acquire multiple locks in a different order.

    Example of Deadlock:

    using System;
    using System.Threading;
    
    class Program
    {
        static object lock1 = new object();
        static object lock2 = new object();
    
        static void Main()
        {
            Thread thread1 = new Thread(DoWork1);
            Thread thread2 = new Thread(DoWork2);
    
            thread1.Start();
            thread2.Start();
    
            thread1.Join();
            thread2.Join();
        }
    
        static void DoWork1()
        {
            lock (lock1)
            {
                Console.WriteLine("Thread 1 acquired lock 1");
                Thread.Sleep(100); // Simulate some work
                lock (lock2)
                {
                    Console.WriteLine("Thread 1 acquired lock 2");
                }
            }
        }
    
        static void DoWork2()
        {
            lock (lock2)
            {
                Console.WriteLine("Thread 2 acquired lock 2");
                Thread.Sleep(100); // Simulate some work
                lock (lock1)
                {
                    Console.WriteLine("Thread 2 acquired lock 1");
                }
            }
        }
    }
    
    • Deadlock Prevention: Always acquire locks in the same order in all threads to avoid circular dependencies.

    Starvation

    Starvation occurs when a thread is perpetually denied access to the resource it needs because other threads are always acquiring it first. It can be avoided by using proper thread scheduling and prioritization.


    4. Conclusion

    • Threads are fundamental to building responsive and efficient applications in C#. They allow for parallel execution and can significantly improve performance.
    • Synchronization ensures safe access to shared resources, preventing issues such as race conditions and data corruption.
    • C# provides several synchronization techniques, including lock, Monitor, Mutex, and Semaphore.

    By understanding how to work with

    Previous topic 14
    Dynamic Link Libraries (DLLs)
    Next topic 16
    Network Programming

    Past Papers

    Open this section to load past papers

    Click on Show Past Papers to see past papers.
    On This Page
      Reading Stats
      Est. reading time7 min
      Word count1,231
      Code examples0
      DifficultyIntermediate