Mutex Vs Condition Variables

Mutex Vs Condition Variables

A Common Confusion Point among many is -

What is the difference between Mutex and Condition Variable ??What exactly is Condition Variable, and when to Use it and how ? If i can Protect the Critical Section using Mutex alone, when do I need to use Condition Variables?

Well, I will not repeat the bookish or Wikipedia definition again. I believe you have already done enough research to get the understanding, yet you are still confused. Let me try to explain with the help of analogies.

First of all, yes, your confusion is justified as the two ( Mutex and CV ) are not easily understood concepts when it comes to juxtaposing them. Mutex and CV are not substitutes for each other, but in fact, they supplement each other in implementing thread synchronization algorithms. One is not enough without the other. In a few simple cases, Mutex alone is enough, but you need a combination of both — CV and Mutex together to handle more complicated cases of Thread/Process Synchronization. Mutexes can be used alone, but CVs need mutexes mandatorily for them to work. Mutex and CV go hand-in-hand while implementing sophisticated thread synchronization data structures such as?Wait Queues, Monitors, Barriers etc.

No alt text provided for this image









Coming to analogies ….

Consider Mutex as a?Lock !?A Simple Lock that you use in day-to-day life. You lock your home doors when you do out, don’t you?

What are the properties of a typical lock :

  1. Lock only once — It can be locked only once.
  2. A Locked-lock cannot be locked again, let’s assume he who tries to lock the already locked-lock again will freeze. It was his punishment for being foolish.
  3. He who locked the lock can only unlock the lock. To put it in other words, only the one who has the keys can unlock the lock.
  4. Unlocking the unlocked lock is again a foolish thing to do, Let’s be gentle, Ignore such a gentleman whoever tries to do so.

Mutex?exactly has properties similar to a typical door-lock. A Thread locks the Mutex ( like a typical door-lock ) before entering the critical section (C.S). Here C.S can be thought of as a house protected by a main-door lock. A thread once finished with C.S, must release the lock so that the next thread enters the C.S. A thread that tries to lock the already locked-ed mutex would have to wait ( it gets blocked at?lock()?call right away). Once the thread ( which has already taken a lock on mutex ) release the Lock on a mutex, one of the waiting thread is granted a lock, and the waiting thread resumes its execution and enters C.S.

Think of this situation with the?famous toilet example. There could be multiple people waiting outside the toilet, but only one can use it at a time. Here Toilet is a Critical Section, one who is using the Toilet is the thread that has a mutex, and those waiting outside the toilet are those who are blocked from granting a mutex ( because mutex is already taken ). So, Mutex gives Exclusivity.

Condition Variable ( CV ) ,?on the other hand, is also used to block a thread. So, here is a point of confusion, if the CV is used to block a thread, then what for mutex is used. We just read mutex also blocks the thread.

Let me re-iterate again, CV is used to block a thread based on a custom condition, whereas mutex is used to block a thread based on only one condition and that is whether the mutex is already locked or not. Mutexes cannot be used alone to block threads based on user-defined conditions. For example,

???You would want to wait from having a delicious meal unless it is cooked fully

???You would not buy expensive headphones until your next salary is credited

In the above two scenarios, there is a condition involved. You have to wait until some condition is satisfied. Whenever you need to block a thread based on some explicit/user-defined condition, CV comes to the rescue. The condition is technically called a?predicate.

The below code snippet demonstrates the use of CV and Mutex together to implement thread-blocking based on a user-defined condition — that the thread must block until the meal is fully cooked. Let me explain it line by line.

1.lock (meal->mutex);

2. while (meal_not_fully_cooked(meal) == false) {

3.   wait (meal->CV, meal->mutex);

4. }

5. unlock (meal->mutex);

6. enjoy_meal(meal);
        

The above code is technically called a?Consumer Code. First of all,?meal?has two members —?mutex and CV. Any object which needs protection again thread-unsafe access needs to have?private mutex. Here Mutex is used to lock the?state of the meal?so that while the thread checks the state of the meal (whether cooked or not) in a while condition (why while() and not if ? We will discuss shortly ), it is ensured that no other thread in the system modifies the meal state. Mutex is used for?exclusivity. A CV can be thought of as a barrier point for threads. Here, when the thread checks that the meal is not fully ready, it blocks on the CV ( Barrier ). Multiple threads that execute the above code and if find the meal is not fully cooked shall be blocked on the same CV.

Now,?wait()?system call has a very special thing attached to it. When the?wait()?is invoked by a thread, two things happen?atomically?:

???The calling thread gets blocked on CV

???the mutex ( 2nd argument to wait () ) gets unlocked automatically and becomes available

It is important that when the thread blocks at?wait() ,?Mutex?must?be released, otherwise no other thread would ever be able to execute line 2 since it gets blocked right away at line 1 because mutex was already taken by a thread blocked on line 3.?If a Girl rejects your proposal, let at least somebody else try!!

Now. Assuming the meal is fully cooked. Somebody in the house has to shout out — “Hey Meal is ready”, while you and your father are waiting on the dining table ( = read it as threads blocked on CV on executing wait() ). So, the maid of the house checks the meal state and shouts with joy,?the meal is ready!!?Equivalent to?—?Some other thread in the system (which is active and not blocked on any CV obviously ) checks the meal state and finds the meal is fully cooked. The maid announces to you all, the meal is ready!! Here,?announce?means, the maid-thread ( which finds the meal ready ) sends signals to the blocked threads so that blocked threads can resume Or wake up. The code to send the signal will be :


1. lock (meal->mutex);

2. if (!meal_not_fully_cooked(meal)) {

3.   signal (meal->cv);

4. }

5. unlock (meal->mutex);
        

The above code snippet is referred to as?Producer Code.?Sending the signal is also sandwiched between lock() and unlock() of a mutex, like wait(), for the same reason — when the maid-thread checks the state of the meal, no other thread in the system must modify the state of the meal meanwhile. Checking the state of the resource should always be exclusive ( lock the mutex and then check the state of the resource ).

Here, maid-thread is a producer thread, while father-thread and you-thread are consumer threads.

Now, coming to the signaling part. When the maid-thread sends the signals, one of the threads blocked on the same CV on which the signal() is invoked shall be woken up by the OS scheduler. Now there are two rules associated with waking up a thread blocked on a CV :

???Thread is woken up only when some other thread in the system generates a signal

???The woken-up thread will be given a lock on a mutex again automatically.

Since the wait() call is within a while block, the woken up thread re-iterates and checks the state of the meal again. This is important to ensure that the maid-thread announcement was not actually a lie. This is called?avoidance of spurious wake-up.?Take it as a rule, always use?while( )?instead of?if?to avoid spurious wake-ups in the Consumer Code. There is a finite time lapse between the maid-thread generating a signal, and the signal being delivered to the blocked thread. In between this time-lapse, the state of the meal could be changed by some thread in the system, and that is the root cause of spurious wakeups. So when blocked threads wake up, they must check the condition again to ensure they are woken up for the right reasons, if not, block again. All this is handled by a simple?while()?statement. The goal is — threads must invoke enjoy_meal() only and only if the meal is ready.

Spurious Wake up Examples -

You wake up only to find that whatever you were promised ( or told ) has been broken ( or is a lie )

Eg1:?You receive a phone call from your dearest friend, and he invites you for a booze party at a common friend’s house. While you are on the way, the common friend leaves the town out of some emergency, he did not inform you. You reach his home only to find your common friend nowhere à Spurious Wake-up. It takes a finite amount of time for you to reach your friend’s house, and within that time interval, the situation changed such that it was futile for you to reach your friend’s house.

Eg2:?Your Father bought sweets exclusively for you, he advised you to consume them after having a bathe. You go for a bath your sibling over-heard this conversation, and eat all the sweets in the box, leaving the box empty. You come out of the washroom, only to find the sweet box empty (against your expectation) à Spurious Wake up. You are sad and angry!

Having understood Spurious wake ups ..

Now there was two-person waiting at the dining table — you and your father. Read it as — two threads were blocked on meal->CV. When maid-thread generates a signal, only one of you ( you or your father ) would be going to wake up since there is only one signal. It is not good that only one of you enjoys the delicious meal, after all, you are family. So, to make both of you enjoy the delicious meal together, the maid thread either needs to generate two signals ( i.e. invoke?signal()?call twice Or simply invoke?broadcast(meal-CV)?which will take care to unblock all threads on a given CV ( Read it as — you and your father together will resume but?one-by-one)?and eventually start enjoying the meal together by invoking?enjoy_meal()?function.


1. lock (meal->mutex);

2. if (!meal_not_fully_cooked(meal)) {

3.   broadcast (meal->cv);

4. }

5. unlock (meal->mutex);
        

Here one-by-one means — when multiple threads blocked on a CV are woken up together, they actually resume their execution one-by-one i.e. sequentially. In this case, first, the father thread wakes up, and then the “you-thread” wakes up (The sequence of waking up the threads blocked on a CV is random and is chosen by the OS scheduler, I have just chosen the father thread first randomly). The below Image somewhat captures the essence of threads waking up one by one from a barrier ( CV ).

No alt text provided for this image

If we assume that toll-gate for the moment stops toll-tax collection, and allows all waiting cars to proceed freely, the cars would cross the toll-tax one-by-one anyway, though all are in a moving state. Exactly the same way, multiple blocked threads get unlocked on a CV. Until the woken-up thread releases the mutex by invoking unlock (meal->mutex) explicitly, the other ready-to-move thread (which has received the signal, but not yet started execution) does not actually resume.

Remember, Checking the meal-state ( In the consumer code) is a critical section, no two threads can be in an execution state, even before blocking on CV or after waking up on CV. This is the reason that to?preserve the principle of mutual exclusion, the just-woken-up thread is given the lock on mutex automatically so that only one thread wakes up at a time.

Lastly, I have assumed enjoy_meal() fn is?read-only?fn, so that you and your father can enjoy the meal in parallel. Had enjoy_meal() was assumed to be?write fn?execution of which changes the state of the program, then in Producer code, enjoy_meal() would have been placed inside C.S. itself as shown in below code snippet so that only one thread access this fn at a time ( = read it like only you or your father can have a meal at a time, not together ).


1.lock (meal->mutex);

2. while (meal_not_fully_cooked(meal) == false) {

3.   wait (meal->CV, meal->mutex);

4. }

6. enjoy_meal(meal);

5. unlock (meal->mutex);
        

Conclusion

In the above discussion, we realize that Mutex and CVs work together to implement mutual exclusion and voluntary blocking/signaling of threads — at the same time. The two thread synchronization primitives have completely different purposes and are used together to implement user-defined thread synchronization schemes. In fact, Mutex/CV are fundamental to controlling the thread’s execution flow and forms the building blocks that can be used to implement any more complicated thread synchronization schemes/data structures such as?Thread Monitors, Wait-Queues, Read-Write Locks, recursive mutexes, Barriers, Assembly line, Event-loops?and many more. You need nothing more than Mutexes and CVs. So powerful the combination is. In fact, I have created a course —?Multithreading Design Patterns in C/C++?in which I show how mutex & CV can be used cleverly to build the above-mentioned thread synchronization schemes. Have a look, Course is a bit advanced.


▂ ▄ ▅ ▆ ▇ █ █ ▇ ▆ ▅ ▄ ▂ ▁▁ ▂ ▄ ▅ ▆ ▇ █ █ ▇ ▆

Want to get access to the Full Course?

website?www.csepracticals.com, We offer 20 Courses on System Programming, Operating Systems, Network Programming, and Development Projects.?

You can enroll in all our courses for free?here?for a?30 days trial. You will have complete access for 30 days, including this course.

Join us :

Telegram Grp (600+ System Programming Engineers ) :?https://t.me/telecsepracticals

Sign up with us?here


Our Course list, visit?www.csepracticals.com

1. Part A — Multithreading & Thread Synchronization in C (14h 11m)

2. Part B (ADV) Multithreading Design Patterns in C/C++ (8h 40m)

3. Part A — Networking Projects — Implement TCP/IP Stack in C (14h 20m)

4. Part B — Networking Projects — Implement TCP/IP Stack in C (8h 12m)

5. Operating System Project — Develop Heap Memory Manager in C (7h 22m)

6. Linux Kernel Programming — IPC b/w Userspace and KernelSpace (2h 48m)

7. Coding Project — Programming Finite State Machines (2h 0m)

8. Master Class : TCP/IP Mechanics from Scratch to Expert (9h 3m)

9. System C Project — Write a Garbage Collector from Scratch (3h 35m)

10. Build Remote Procedure Calls (RPC) — from scratch in C (6h 15m)

11. Linux System Programming Techniques & Concepts (14h 0m)

12. Linux Inter-Process Communication (IPC) from Scratch in C (8h 41m)

13. Network Concepts and Programming from Scratch — Academic LvL (22h 59m)

14. Integrate CLI interface to your C/C++ Projects Quickly (1h 20m)

15. System C/C++ Course on Linux Timers Implementation & Design (3h 48m)

16. Part A — Networking Project — Protocol Development from Scratch (14h 20m)

17. Part B — Networking Project — Protocol Development from Scratch (7h 54m)

18. Asynchronous Programming Design Patterns — C/C++ (5h 11m)

19. Advanced TCP/IP Socket Programming in C/C++ ( Posix ) (4h 30m)

20. Network Security — Implement L3 Routing Table and Access Control List (1h 48m)

要查看或添加评论,请登录

Abhishek Sagar ????的更多文章

  • SQL Query Order Execution

    SQL Query Order Execution

    Have you ever wondered how a typical SQL query is executed by the SQL engine of a relational database system? In this…

    1 条评论
  • What does it take to Implement a Network Protocol?

    What does it take to Implement a Network Protocol?

    Routing Protocols are the heart of Networking. They power the Data Center Switching, Internet, clouds, and anything…

  • What does it take to Implement a Network Protocol?

    What does it take to Implement a Network Protocol?

    Routing Protocols are the heart of Networking. They power the Data Center Switching, Internet, clouds, and anything…

  • Demystifying TCP …

    Demystifying TCP …

    TCP is one of the most widely used protocols on the internet, and at the same time very complex too. TCP has decades of…

    3 条评论
  • What are Remote Procedure Calls ( RPCs )?

    What are Remote Procedure Calls ( RPCs )?

    As the name suggests, RPC means, invoking a function/procedure which is implemented and running on a remote machine in…

    2 条评论

社区洞察

其他会员也浏览了