Peterson Lock in a binary tree - concurrency

I have some doubts about Peterson algorithm in a binary tree.
I am making some exercises of the book "The Art of Multiprocessor Programming" and i'm stuck in chapter 2, ex 13:
"Another way to generalize the two-thread Peterson lock is to arrange
a number of 2-thread Peterson locks in a binary tree. Suppose n is a power of two. Each thread is assigned a leaf lock which it shares with one other thread. Each lock
treats one thread as thread 0 and the other as thread 1."
That´s OK, but what? If Peterson only treats 2 threads, how will be this tree? One tree with ONE single leaf? (because if I have 2 threads, and each leaf treats 2 threads... the result will be a tree with a single leaf?)
"In the tree-lock’s acquire method, the thread acquires every two-thread
Peterson lock fromthat thread’s leaf to the root. The tree-lock’s releasemethod for
the tree-lock unlocks each of the 2-thread Peterson locks that thread has acquired,
from the root back to its leaf."
What did he mean with that? How can a leaf go through the root node? Very confused!! :S
Thank you guys!

The generalization to use n two-thread Peterson locks and arrange them in a binary tree works as follows:
To acquire the lock:
Suppose that there are n threads that want to access to a critical region.
The first step uses n/2 two-threads Peterson locks. Then two threads are assigned for each two-threads Peterson lock. At the end of this step only n/2 threads acquired the lock. Those n/2 two-threads Peterson locks are the leaves of the binary tree
Similar to the first step, the second step uses n/4 two-threads Peterson locks and two threads are assigned for each Peterson lock (those threads are the "winners" in the first step). Those n/4 Peterson locks are the new nodes of the tree
This procedure continues up to it reaches the root, where it's necessary only one Peterson lock. The thread that acquires the last Peterson lock can enter to the critical region.
To release the lock
The thread that acquired the lock must release each Peterson lock in the path that it followed from the corresponding leaf to the root.
I hope this explanation will serve you.

Related

Is it possible a lock wouldn't release in a while loop

I have two threads using a common semaphore to conduct some processing. What I noticed is Thread 1 appears to hog the semaphore, and thread 2 is never able to acquire it. My running theory is maybe through compiler optimization/thread priority, somehow it just keeps giving it to thread 1.
Thread 1:
while(condition) {
mySemaphore->aquire();
//do some stuff
mySemaphore->release();
}
Thread 2:
mySemaphore->aquire();
//block of code i never reach...
mySemaphore->release();
As soon as I add a delay before Thread 1s next iteration, it allows thread 2 in. Which I think confirms my theory.
Basically for this to work I might need some sort of ordering aware lock. Does my reasoning make sense?

Dining philosophers using binary semaphores

Can this pseudocode solve the dining philosopher problem with maximum parallelism? Here mutex is a binary semaphore initialized to 1. Forks are assumed to be numbered from 0 to (N-1). There are a total of N philosophers numbered from 0 to (N-1).
void philosopher(int i) // ith philosopher
{
while (true)
{
think();
down(&mutex); // acquire lock
take_fork(i); // take left fork
take_fork((i+1)%N); // take right fork
up(&mutex); // release the lock
eat();
down(&mutex); // acquire lock
put_fork(i);
put_fork((i+1)%N);
up(&mutex); // release the lock
}
}
This should solve the dining philosopher problem with maximum parallelism, because the lock is released after one philosopher has acquired both forks. But will it? And will there be any issue of liveliness? I am confused.
To answer your question I would like to provide a trace of events that seems to lead your philosophers into an undesired state.
Consider a system with N>2 philosophers Ph(0),...,Ph(N-1) and the following sequence of actions:
Ph(1).think();
Ph(0).think();
Ph(1).down(&mutex);
Ph(1).take_fork(1);
Ph(1).take_fork(2);
Ph(1).up(&mutex);
Ph(0).down(&mutex);
Ph(0).take_fork(0);
Ph(0).take_fork(1);
Recall that fork(1) is already acquired by Ph(1). Now depending on the semantics of take_fork() different behavior can occur.
If take_fork() fails immediately if the fork cannot be acquired, then fork(0) would never be released.
If take_fork() hangs until the resource is released, then the mutex would never be released and none of the other philosophers would be able to make progress therefore only one philosopher will be eating at a time.

Lock two mutex at same time

I'm trying to implement a multi-in multi-out interthread channel class. I have three mutexes: full locks when buffer is full. empty locks when buffer is empty. th locks when anyone else is modifying buffer. My single IO program looks like
operator<<(...){
full.lock() // locks when trying to push to full buffer
full.unlock() // either it's locked or not, unlock it
th.lock()
...
empty.unlock() // it won't be empty
if(...)full.lock() // it might be full
th.unlock()
operator>>(...){
// symmetric
}
This works totally fine for single IO. But for multiple IO, when consumer thread unlocks full, all provider thread will go down, only one will obtain th and buffer might be full again because of that single thread, while there's no full check anymore. I can add a full.lock() again of course, but this is endless. Is there anyway to lock full and th at same time? I do see a similar question about this, but I don't see order is the problem here.
Yes, use std::lock(full , th);, this could avoid some deadlocks
for example:
thread1:
full.lock();
th.lock();
thread2:
th.lock();
full.lock();
this could cause a deadlock, but the following don't:
thread1:
std::lock(full, th);
thread2:
std::lock(th, full);
No, you can't atomically lock two mutexes.
Additionally, it looks like you are locking a mutex in one thread and then unlocking it in another. That's not allowed.
I suggest switching to condition variables for this problem. Note that it's perfectly fine to have one mutex associated with multiple condition variables.
No, you cannot lock two mutexes at once, but you can use a std::condition_variable for the waiting threads and invoke notify_one when you are done.
See here for further details.
Functonality you try to achieve would require something similar to System V semaphores, where group of operations on semaphors could be applied atomically. In your case you would have 3 semaphores:
semaphore 1 - locking, initialized to 0
semaphore 2 - counter of available data, initialized to 0
semaphore 3 - counter of available buffers, initialized how much buffers you have
then push operation would do this group to lock:
check semaphore 1 is 0
increase semaphore 1 by +1
increase semaphore 2 by +1
decrease semaphore 3 by -1
then
decrease semaphore 1 by -1
to unlock. then to pull data first group would be changed to:
check semaphore 1 is 0
increase semaphore 1 by +1
decrease semaphore 2 by -1
increase semaphore 3 by +1
unlock is the same as before. Using mutexes, which are special case semaphores most probably would not solve your problem this way. First of all they are binary ie only have 2 states but more important API does not provide group operations on them. So you either find semaphore implementation for your platform or use single mutex with condition variable(s) to signal waiting threads that data or buffer is available.

if (lock) wait else lock=1 CS lock=0

Consider this possible implementation of a synchronization mechanism, the goal being that only one thread is in the critical section (CS) at a time:
if (lock)
wait
else
lock=1
CS
lock=0
Will this work for a multiprocessor system?
No. This won't even work on a single-processor system. Suppose you have two threads doing this. They could be scheduled this way:
thread1 thread2
if (lock) /*false*/
if (lock) /*false*/
lock=1
lock=1
/*CS*/
/*CS*/
/*CS continues*/
/*CS continues*/
lock=0
lock=0
You can't implement a lock this way. One of the ways to implement a lock is with a processor instructions that atomically checks the current value of the variable and assigns to it, such as test-and-set. (There are many other possible ways, different CPUs provide different methods and sometimes more than one.)

Counting semaphores initialized to n

If a counting semaphore is initialized to n, does it mean n processes can run their critical sections concurrently?
Essentially, yes.
Remember a counting semaphore will only block when the count is negative after decrementing. Therefore, the semaphore can be decremented n times before blocking. Since all decrements must be matched with an increment, then, assuming each process decrements the semaphore only once (which is, by far, the most common case), then yes, n processes will be able to run their critical sections at the same time.
No.
If n > 0, then it means that the counting semaphore can be taken exactly n times before the requesting context blocks and waits for the counting semaphore to become available (assuming no one gives it during that period).
If n <= 0, then it means that that counting semaphore must be given (1 - n) times before anyone can successfully take that counting semaphore.
Controlling access to a critical section is typically better handled by a mutex.
Yes, if you have initialized semaphore to N, then sem_wait will not block any thread unless it has already been called N times, then only semaphore becomes -ve and that's when any thread calling sem_wait blocks.
for critical section you have to use binary semaphores or mutex.