1

Why do we need semaphore, condition variables and other constructs. I get that a thread getting blocked until a signal is better than a thread that takes mutex lock, checks for a condition which if doesn't pass, unlock and retry till the condition passes.
Other than this, is there any other benefit. As an example, can we implement a read-write lock using only mutex... Something as below:

int writers = 0;
int readers = 0;
mutex lock;

read_lock()
{
    mutex_lock(&lock);
    while(writers == 1)
    {
        mutex_unlock(&lock);  // unlock
        mutex_lock(&lock);    // retry
    }
    readers++;
    mutex_unlock(&lock);    
}

read_unlock()
{
    mutex_lock(&lock);
    readers--;
    mutex_unlock(&lock);
}

write_lock()
{
    mutex_lock(&lock);
    while(r>0 || w==1)
    {
        mutex_unlock(&lock);  // unlock
        mutex_lock(&lock);    // retry
    }
    writers++;
    mutex_unlock(&lock);      
}

write_unlock()
{
    mutex_lock(&lock);
    writers--;
    mutex_unlock(&lock);
}

1) Will the above implementation even work? If not, why?
2) Can I say that everything in multi-threading can be done using just mutex?

Parzival
  • 585
  • 2
  • 12
  • Are you asking why a busy-wait loop like the one you propose is not acceptable? Because it's a waste of resources, and it doesn't give a chance for lower-priority tasks to execute while your high-priority task is burning away cycles checking for a condition that may never become true. – n. m. could be an AI Aug 24 '18 at 10:14
  • 1
    Regarding (1), did it work when you tested it? Thoroughly? Why stop at just mutexes. Why not just ditch the mutex and spin on interlocked atomics? Winter is coming, you could even heat the office with the dissipated thermals. Seriously though, are you just curious, or are you actually considering doing this? The whole point of those constructs is to yield the CPU to a better keeper of it than you; namely the OS and its kernel, in a responsible manner. Imagine a long, LONG, writer hold. How many needless cycles will be literally burned into ether by readers in such a case. – WhozCraig Aug 24 '18 at 10:14
  • Re, "... and retry till the condition passes." What you are describing there is called "busy waiting" or "polling" or "spinning." Waiting on a condition variable usually is better than spinning if your user-mode process will run alongside other processes on a desktop, server, or mobile platform; but there can be legitimate reasons for spinning, especially if you are writing kernel-mode code or, if you are writing for an embedded system. – Solomon Slow Aug 24 '18 at 13:54

2 Answers2

2

A mutex is designed to provide protection on a shared(between threads) variable.It's not designed to provide sychronization.

I'll try to give an example.Let's assume that we have a linked list that we need to share and we need to Spin up 10 threads - have 5 of the threads be responsible for writing to the linked list, and 5 of the threads be responsible for reading from the linked list. We have to treat the linked list as a queue (FIFO) - write to the end of the list, read from the front of the list All threads require exclusive access to the linked list (since readers will remove items from the head of the queue as writer add to the tail of the queue) and also have a random latency for calculations (or simply assume that they need to sleep) The question now:HOW are we going to ensure the integrity of the head and foot of the queue??

Seems impossible using only mutex right? That's why NO , mutex is not enough at all cases.

In my case we'll need a Conditional Variable Using those variables and notify other threads via a broadcast (or even individually) we will be able to achieve synchronization between our threads ensuring the integrity of the head and foot we share.

PS:Another good point of view is this. PS

0

Re, "Why do we need ... constructs?"

Because, layers of abstraction and, code reuse.

You don't need to have a blocking queue, for example. If you have arrays and pointers and mutex and condition variable, then you can use them to implement anything that you could have implemented with a blocking queue. But your code will be a lot harder to read because, in every place where you could have just called q.put(...) or q.take(), you will have to write a whole bunch of lines of code instead.

A blocking queue is a useful abstraction: It wraps up lower-level ideas into a simpler, higher-level function.

You don't need a semaphore if you have a blocking queue. Anything you can do with a semaphore, you can do by putting meaningless tokens into a queue and then removing them. A semaphore really is a simpler version of the queue idea, and it may be a closer fit to (a better description of) the problem that you are trying to solve.

Also, you don't need to use anybody else's implementations of blocking queue or semaphore: You can always write your own, but why do the extra work? (except, maybe for educational purposes.)

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57