2.
Arrange expectations according to requirement 2 (manufacturer's
expectations in case of full buffer). In doing so, the consumer must
inform the expectant manufacturers that he has taken the object from
the buffer (ie the buffer has become incomplete, if complete)
3.
Arrange expectations according to requirement 3 (
consumer
expectations in case of full buffer). In doing so,
the manufacturer
must inform consumers who expected that the matched buffer is non-
empty and if it was empty.
Now let's look at the synchronization primitives we need. Each
synchronization operation requires a separate semaphore:
1.
To use the critical section, we use the binary semaphore, as we
did before. Let's call it to lock. It will be used by both the
manufacturer and the consumer, protecting access to the buffer from
other threads (again, both manufacturers and consumers)
2.
In order to organize the manufacturer's expectations in the case
of a full buffer, we will need a semaphore whose current value is
equal to the number of free spaces in the buffer. Let's call
it empty _ items. The manufacturer, before attempting to add a new
object to the buffer, reduces this semaphore to the standby state if it
is equal to 0.
The consumer, after removing the object from the
buffer, will increase the semaphore by informing the manufacturers (
and waking up one of them)
3.
In order to organize consumer expectations in the case of an
empty buffer, a semaphore whose current value is equal to the
number of seats in the buffer will be required. Let's call
it full _ items. The consumer before attempting to pick up an
object
from the buffer reduces this semaphore, going
into a state of waiting
Here is the pseudocode solution to this problem:
semaphore _ t lock = 1; // for the
critical section
semaphore _ t empty _ items = n;// 0 buffer full, empty from the
beginning
semaphore_t full_items = 0; // if 0 - the buffer is empty
// manufacture r
void producer ()
{
item_t item = produce (); // create about ' object
down (empty_otems); // is there a place for information ' facility?
down ( lock ); // enter the critical section
append_to_buffer (item); // add about ' object item to the clipboard
up (lock); // exit critical section
up ( full _ items ); // inform consumers that there is a new object
}
// consumer
void consumer ()
{
item_t item;
down ( full _ items ); // not empty buffer?
down ( lock ); // enter the critical section
item = receive_from_buffer (); // pick about ' Object item from the
clipboard
up ( lock ); // exit critical section
up ( empty _ items ); // notify manufacturers of the location
consume ( item ); //
consume the object
}
Mutexes
The concept of mutex largely coincides with the notion of blocking defined
previously (Section 5.2). Synchronization primitives called Mutexes are the
ones that prevent the execution of a piece of code in more than one
stream. In fact, the mutex is an implementation of OS-level blocking.
Mutex, as its name implies, eliminates mutual exclusion. Its main task is to
block all threads trying to access the code when that code is already
executing a thread. A mutex can be in two states: the free and the busy. The
initial state is "free" and has two possible atomic operations.
❖
Take mutex ( mutex _ lock ): if mutex was free, he is busy, and the
flow continues its execution (entering the critical section); if the
mutex was busy, the stream enters a standby state (said to be a "wait
on a mutex" or "locked on a mutex"), the execution continues another
stream. Stream, which took mutex called
the owner of the mutex:
mutex _ lock ( mutex _ t mutex )
{
if (mutex.state ==== free)
{
mutex.state = locked;
mutex.owner = this_thread;
}
else
sleep ();
}
❖
To release the mutex ( mutex _ unlock ): mutex becomes free; if
several
streams are expected on it, one of them is selected, it starts
executing, occupies the mutex and enters the critical section. In most
implementations, the flow selection will be random. Only the owner
can release the mutex. Here is the pseudocode of this operation:
mutex_unlock (mutex_t mutex)
{
if (mutex.owner! = this_thread)
return error;
mutex.state = free;
if (waiting_threads ())
wakeup (some_thread);
}
Some implementations provide a third operation try to take the mutex
( mutex_trylock): if the mutex is free, to act similarly to mutex_lock, if
busy - to immediately return the error and continue execution.
Here is the simplest implementation of the critical section using a mutex.
mutex_t mutex;
mutex_lock (mutex);
// critical section
mutex_unlock (mutex);
The main difference in the mutex of binary semaphores is that the release
of mutex can only be done by its owner, while the semaphore can change
the value of any stream, which has access to it.