The next topic that ties, processes and threads together is synchronization.
Usually you have processes and threads executing concurrently or in parallel.
When you do this, it sometimes causes issues when you are using shared data between them.
In this case, if you have two processes sharing the same set of data, they cannot access the same
data at the same time because it will cause data integrity issues in order to solve this issue.
We will be using some performers to accomplish synchronization.
An important topic when dealing with synchronization is critical sections.
A critical section occurs when you have multiple threads or processes, all competing to use the same
share data.
This problem creates a need for the processes to cooperate by requesting permissions to enter its critical
section.
In short, a critical section is an area of code where only one process can execute that particular
code at a time.
This creates a mutual exclusion for that code block.
So, for example, as you can see here, we have different processes or threads represented by these
code box.
This is where you enter the critical section and then you perform the shared data access and then you
leave the critical section after this point.
What this is saying if you have separate processes or threads and each of these diagrams represent one
processor thread, only one of these can enter this shared data access at a time, and it has to wait
its turn to access that data.
In order to do that, we use some air force in order to solve these synchronization problems.
Semaphore is are essentially an abstract data type used to access shared data or resources.
And they use what is called a semaphore account.
A Semaphore account is essentially a value that either allows the process or third to enter the critical
section or not.
There are two main methods used in semaphore.
The first one is signal, and the other one is wait.
The first method is Signal, as you can see, when you are using this method, you have to include the
library semaphore dot h.
And for now, this is how I will be representing the semaphore type.
Some underscore T.S..
This is what we will call our semaphore.
When you call the signal method on your semaphore, it essentially increases the value of S by one s
equals s plus one.
The next method that we need to know how to use is weight.
We have our semaphore data type again and here is where we are using the weights method.
The first thing that we are doing is we are entering a while loop until TSS is not less than or equal
to zero.
So while she is less than or equal to zero, this method will do nothing, and with saying that the
process will also do nothing.
However, for instance, what you would call a signal which will increase as +1.
So if I start a zero and then you call Signal, that means this is now one, which means us is not less
than or equal to zero, which means it will decrease it to zero.
It's a kind of weird concept how there are only two methods, and it's very simple of signal increases
the semaphore and weight decreases the semaphore.
But I think it will be more clear when we see actual examples.
Synchronisation and semi force are used in many different types of problems.
These problems have a lot of concurrency control, and we will use some force for synchronization.
These are the most common problems that are known in the computer science.
The first one is the bounder buffer problem, which will cover the readers, writers, dining philosophers,
sleeping barber, etc. There's a lot of different variations of this problem of the bounded buffer problem,
but you can look this up on your own.
But for now, I'm going to take you over the basis of what the boundary buffer problem it.
The bounded buffer problem is essentially a producer consumer problem.
There are two processes running at the same time a producer and consumer.
Both are operating and perform actions on a buffer, which is shared data in order to explain.
In simple terms, we essentially have a producer, which is one process that is producing one item each
iteration, and this item will just be added to the shared buffer.
And we have another process that is the consumer, and this consumer consumes one item each generation.
So that sounds very simple.
You just have one producer and one consumer consumer one item at a time, but it gets a little more
complex.
Say you have one producer and five consumers.
How is that going to work out if there are for other consumers just waiting for the producer?
That is where some efforts come into place and we have to make sure that the producer and consumer are
not accessing the shared buffer at the same time.
And there are a lot of restrictions when you're dealing with this problem.
The first one is that the buffer can only hold any items.
So say you have a buffer of size 10 and the producer is producing them faster than the consumers, which
means that the buffer will get full eventually.
So you have to take that into account, and you can only produce when the buffer has available space.
Likewise, you can only consume when there is something in the buffer that can be consumed, and we
will be using some Air Force to accomplish this to get started.
I want to first show some pseudocode forthe producer consumer problem.
The first thing that you need to understand is the shared data between these two processes.
The first semaphore that we'll be using is a new tax, which is one, and this is essentially a lock
that will be subtracted one or added one.
And it will allow either the producer to enter the critical section or the consumer to enter the critical
section.
On the outskirts of the critical section area, we have a mutex and a mutex.
Likewise, on the consumer side, we have a new tax and a new tax, and these are both right on the
outside of the critical section.
The next semaphore that we need to use is an empty semaphore.
And this is initialized to the buffer size.
Next is a semaphore fool, and we start this at zero unless we have a buffer of size buffer size.
So first, I want to walk through the producer.
This producer will first wait empty and remember, we are starting with empty.
That is the size of the buffer.
So if the size of the buffer is 10, then that means that weight 10 and 10 is greater than or equal
to zero.
And if you remember the weight method weights, if it's less than or equal to zero or it continues and
it subtracts one from the semaphore if it's greater than zero.
So in this case, empty is 10 and it can continue.
Next, it weights for the mutex lock.
And in this case, it's one which means it's able to continue and we subtract one from that value.
So it goes the zero, which means that it's in this critical section when it's at zero.
Let's say, for instance, the consumers at this point, this will not be able to enter into the critical
secondary because this mutex is zero, which means that it's in that weighting state where it's in an
infinite while waiting for it to not be zero.
So waiting for it to be greater than zero.
What's the producers in the critical section area?
It inserts an item into the buffer.
In this case, it'll just be an integer.
Next, after it inserts an item into the buffer, it does a signal on the mutex, which means it's unlocking
the critical section.
And what that is doing is increasing the mutex.
So it started out as one.
Then the producer entered the critical section because it decreased the mutex to zero, which means
it's in the critical section and then it increased it when it was done, which means it put it back
to one, which means the producer is done with the critical section and the consumer can use it.
Lastly, it signals the full semaphore, which full starts, it starts out as zero.
And in this case, it just increases by one.
And that is used on the consumer side, which I'll cover in a second.
Now on the consumer side, it waits for full, which means it's waiting for something to be in the buffer
when the producer signals Fool.
It is increasing this number four by one, which means that there is something in the buffer when something
is in the buffer.
Weight is greater than zero, which means it can continue on.
Remember, weight has an infinite loop that will wait until it is greater than zero.
So once the producer signals the fall, which means it increases the semaphore by one.
This means this consumer can move to the next line.
Three.
In this line, we're waiting for the mutex lock, which as I stated in before.
Once the producer actually releases the mutex locks, the consumer can enter the critical section.
Here, the consumer will remove an item from the buffer, and essentially what this is doing is just
removing one integer from the buffer.
Once it does that, it can signal the new text, which means it's unlocked unlocking the mutual exclusion,
and the producer can enter into the critical section if it wants.
And then it's going to signal empty, which means it's going to increase the empty semaphore by one.
And this summer for IS Remember is used by the producer up here, and this will allow the producer to
know when does it need to actually produce something?
And likewise, the full semaphore here lets the consumer know when it can actually consume something.
So now I want to show you a real bounded buffer problem using some force and threads.
OK, now that we are in my VM, I want to just quickly go over a simple, bounded problem.
It is a little long and I don't want to take too much time because this course was supposed to be more
of an introductory course and just to get you interested in this topic and I can make more of its topics
later.
So I was going to go over a quick, simple bounded problem and we can cover it more in depth, if you
would like in the next course.
But for now, I just want to give you the general overview because I really feel they gave a good overview
in these slides.
I want to make a note that this is not my code, and I will put the GitHub link to the repository for
this.
But I thought it was a great example of very simple to understand.
In this example, you are able to enter the number of producers, enter the number of consumers and
enter the buffer capacity.
And what this will allow you to do is enter different parameters and you will see how it interacts with
each other.
So to get started, I just want to compile it, and we're going to do a couple of test.
And remember, we got a compiler with LP thread.
And now that we have air out, we can go ahead and run it out.
So for now, what we're going to do is just have one producer, one consumer and a buffer of one hundred.
There is a consumer it outputs information about what is happening.
So it starts out with a producer one produced 19, so the buffer only contains 19 and then Consumer
two consumes 19 and now the buffer like the zero.
And like we stated before, we do not know in what order these are going to execute.
And what I want to now show is if we've run this again, except now let's put 10 producers and one consumer
and but we still have a buffer capacity of 100.
You'll see that there are producers producing a lot faster than the consumers can consume.
And it's you see our buffer is already pretty full not to 100 yet, but you could see that's going to
get there pretty quickly because the consumer is only consuming one item from the buffer time while
the producer is producing 10 items per iteration.
Likewise, if he's run it the opposite way where there are only one producer and 10 consumers, we will
see that the current buffer length generally stays around zero because there are 10 consumers waiting
to consume the one item that the producer is producing.
So let's go ahead and cover the cold really quickly, as we've done before.
This is how we get user input.
We are simply just allocating enough space in memory for all the producers, depending on the count.
And just like the consumers and also the above capacity, we're allocating the correct amount of memory.
And this is the first thing that you have to do when you want to use them, after you have to use a
minute to initialize the semaphore.
And as we've done in previous examples, that's how you create producers.
You iterate from zero all the way to the producer count and you create the producers.
As you can see, this thread create command is using a producer's eye, which is a different way to
do.
But that's how we've done it similar.
And we're passing a bill and then it's calling this method called producer.
That method is up here, but we'll cover that in a second.
So all 10 of the producers will be calling this thread safe function next.
The 10 consumers or however, many consumers are being created using these same P3 create command,
and they are also being directed to the consumer method, which I'll show you the second and those will
go on and on and on until they're ready to join.
And remember what this joint command is doing is waiting for them all to finish executing.
And lastly, we want to join all the consumers.
So let's go ahead and take a look at the actual producers and consumers threats that are being run.
As you can see, these are infinite loops and they are set up in the exact same similar way that I showed
you in the slides where it starts with a semaphore wait for the empty amount and then it sets a mutex.
And what this means is that this is the critical section which simply increases the buffer position
and then adding a value to that position.
On the opposite side, the consumer is waiting for the full count and it's waiting for the mutex, and
after that, it's able to allocate the memory space for the actual item it's wants to consume.
And then it's going to consume, see which is it's going to basically put it in C and then it's going
to do the semaphore post, which is equivalent to the semaphore signal.
And it's going to increase the mutex, increase the empty count.
And this is what I went over the example.
So I didn't want to go over too much because I think my pseudocode example was a lot easier and simpler.
But this is also the critical section down here where it will be consuming.
In the critical section, and after it gets to me, attacks like this do the two semaphore weights,
you do any critical section access which is accessing the shared buffer and then you do the semaphore
signal, which in this case there she is with semaphore post and you're also doing volume.
You text to increase it by one and then you're increasing the fill count by one.
And what that does is it allows the consumer to enter into its critical section if the fill count is
greater than equal to one, and the consumer increases the empty count, which allows the producer to
enter into its critical section so they both are reliant on energy.
Other but only one can enter the critical section at once.
And as you can see, they are using this produce command and also the consume method.
These methods are pretty simple and they're up here.
This producer is essentially just producing one item, and the consumer is just consuming one item,
and it's telling you which consumer is consuming what item.
So I hope this was a OK explanation of it, I want you to take this coat and definitely look it over
and try to do different parameters when you run the program.
It's kind of fun to do it with small buffers, large buffers and do with a lot of consumers and little
producers, a lot of producers and little consumers and so on.
It is really will help you with your understanding of what's actually happening.
But at the end of the day, it all comes down to what was on the slide deck on how they are essentially
just waiting to enter their critical section, and they can only enter that critical section when the
empty count or in this case, the fill count allows them.
Because this summer, four way command will stay here forever until this fill count is greater than
or equal to one.
This is the end of this course.
This is the first course that I have taught.
So if you have any questions or any lessons requests, please let me know.
I really do hope that I conveyed and presented the material in a very understandable and easy way,
but this was my first time.
If you have any feedback on how I can improve my courses that let me know.
But if you have any lesson requests in any field of systems, programming, programming in general and
especially parallel computing, let me know because if there's enough interest in that, I'll be sure
to make it.
So I want to thank you again for taking this course, and I'll see you in the next one.