Four approaches: Passive vs Active Blocking
Approach #1: Condition specific mutexes\nThis approach uses a mutex for each of the two dependent function calls. At the beginning, all the locks are claimed in
Approach #1: Condition specific mutexes\nThis approach uses a mutex for each of the two dependent function calls. At the beginning, all the locks are claimed in the constructor. This ensures that none of the funcitons of requiring a lock are able to grasp or get ownership of the mutex. When they attempt to (with their .lock() call, their thread is blocked until that mutex is released. The only way a mutex can be released is with a call to .unlock, which is called at the end of first. So when first finishes executing, then the lock can finally be aquired by second (with the call to .lock). This function then releases the third lock so that the third_lock mutex can be aquired by third, and the flow can finish. The downside to this approach is that second and third functions are using busy-waitting/active-waitting/spinning for unlocking, meaning that the threads will continue to consume CPU resources actively while waitting. For more detail, this poor waitting mechanism requires the thread to repeatedly check a condition in a loop waitting for some event to occur -- which doesn\'t relinquish control of the cpu. The advantage of active waitting is that it leads to very fast response times for the thread to re-initiate (as it doesn\'t have to endure the overhead of being put to sleep and then woken up again).\n\n```\nclass Foo {\npublic:\n    mutex second_lock;\n    mutex third_lock;\n\n    Foo() {\n        second_lock.lock();\n        third_lock.lock();    \n    }\n\n    void first(function<void()> printFirst) {        \n        printFirst();\n        second_lock.unlock();\n    }\n\n    void second(function<void()> printSecond) {\n        second_lock.lock();\n        // printSecond() outputs "second". Do not change or remove this line.\n        printSecond();\n        third_lock.unlock();\n    }\n\n    void third(function<void()> printThird) {\n        third_lock.lock();\n        // printThird() outputs "third". Do not change or remove this line.\n        printThird();\n        \n    }\n};\n```\nApproach #2: Condition variables\nInstead of relying on a mutex for each of the dependent functions, the dependent functions are blocked by condition variables. This utilizes the passive waitting approach so that the waits on the second and third function relinquish cpu control -- hence conserving resources to be utilized on other active threads. \nPlease note that the condition_variables still require a mutex for several reasons\n1. Protecting access to shared data. \nIt is important to note that we still would want synchronization to the writes to the shared memory between the different threads using this object. Relevant to updating first_exec and second_exec.\n2. Atomicity of wait operations\nThe wait operations are reading from shared memory. So, before accessing this shared memory, we want to ensure that the boolean check is on fresh/protected memory. Once the check is performed, the cv wait function is then able to give up the lock until it is signaled to wake up again and re-aqquire the lock. If this action was not atomic, it is possible that the signal to re-perform the check as the conditional variable might not be in a waitting state. This is commonly referred to as a lost wakeup.\n\n```\nclass Foo {\npublic:\n    mutex m;\n    condition_variable cv2, cv3;\n    bool first_exec, second_exec;\n\n    Foo() {\n        first_exec = false;\n        second_exec = false;\n    }\n\n    void first(function<void()> printFirst) {\n        unique_lock<mutex> lock(m);        \n        printFirst();\n        first_exec = true;\n        cv2.notify_one();\n    }\n\n    void second(function<void()> printSecond) {\n        unique_lock<mutex> lock(m);\n        cv2.wait(lock, [this] { return first_exec; });\n        printSecond();\n        second_exec = true;\n        cv3.notify_one();\n    }\n\n    void third(function<void()> printThird) {\n        unique_lock<mutex> lock(m);\n        cv3.wait(lock, [this] { return second_exec; });\n        printThird();\n    }\n};\n```\nApproach #3: Semaphores\nSemaphores are another synchronization pattern that allow threads to signal eachother about occurences of specific events. Each semaphore maintains a count, which is incremented by signaling ("posting" or "releasing") and decremented by waitting ("aquirring"). When a thread tries to decrement the semaphore while it\'s count is zero, the thread becomes block until another thread increments the semaphore. Please not that semaphores also use passive waitting.\n```\nclass Foo {\npublic:\n    sem_t sem2, sem3;\n\n    Foo() {\n    \t// Note that the second argument here is referred to as the Pshared value. It specificies whether or not \n    \t// the semaphore can be used across multiple processes. If it is a non-zero value, the semaphore is allocated in a shared\n    \t// memory segment. If it is zero, then it is only used in the defined process. The third argument is the initialized counter \n    \t// argument (so zero would mean a call to wait would block in a resource efficent manner)\n        sem_init(&sem2, 0, 0);\n        sem_init(&sem3, 0, 0);\n    }\n\n    void first(function<void()> printFirst) {\n        printFirst();\n        // increment/signal the semaphore so that threads trying to decrement/accquire it can continue\n        sem_post(&sem2);\n    }\n\n    void second(function<void()> printSecond) {\n        // wait until this semaphore has a postive value so we can decrement it and continue\n        sem_wait(&sem2);\n        printSecond();\n        sem_post(&sem3);\n    }\n\n    void third(function<void()> printThird) {\n        sem_wait(&sem3);\n        printThird();\n    }\n};\n```\nApproach 4: Using promises\nAnother way to implement passive waitting, using promises.\n```\nclass Foo {\npublic:\n    promise<void> p2, p3;\n    Foo() {\n        \n    }\n\n    void first(function<void()> printFirst) {        \n        printFirst();\n        p2.set_value();\n    }\n\n    void second(function<void()> printSecond) {\n        p2.get_future().wait();        \n        printSecond();\n        p3.set_value();\n    }\n\n    void third(function<void()> printThird) {\n        p3.get_future().wait();       \n        printThird();\n    }\n};\n```
['C++']