Lecture 7 - Thread Schedulers, Race Conditions
This project is a break in the traditional modality of CPE/CSC projects in that you actually have something to show for it.
The snake program runs 5 instances of running a function called indentnum
.
// .. in main()
for(int i = 1; i <= 5; i++)
{
lwp_create((void*)indentnum, i);
}
// ...
If you look at the problem of know knowing where to start; start with just the task/thread scheduler. You can handle a LL right? Yes you can!
The Effect of a Scheduler
Consider the snake programs themselves. Each snake (program) may have more priority over another, but is the scheduler gonna know what exact program to prioritize? NO! You may try to make a heuristic, but that's only going to get you so far.
But before even worrying about the scheduler, let's look at the issue of concurrency, and thus race conditions.
Dealing with Race Conditions and Critical Section
Recall this definition:
With this we have the critical section:
A region of a program that cannot safely be interrupted.
So the requirements for our synchronization solution are that:
- No two processes may simultaneously be in a critical section
- There are no assumptions about the speed or # of CPUs.
We have two other desired properties:
3. No process outside of the critical section should run while a process is in the critical section.
4. No process should be allowed to block forever in the critical section.
Sol'n #1: Busy-waiting Solutions, Software Only
We could try:
- Eliminate concurrency (but no it's too good though).
- Is easy. But concurrency is too good.
- Hope for the best (uhh noo....)
- Sometimes this is fine. But only for tasks that are either not mission critical, or can easily fix themselves in a timely manner
- Turn off interrupts during critical section
- It's foolproof. However, it doesn't allow processes to communicate with each other. I have to lock every single process in order for this to work, which at that point is it really concurrency?
- Furthermore if a process wants to run forever, then it will force the others to stop.
- This really can only be done in the OS, but not anywhere else.
- Software-locked variables
Let's look into (4). You have a variable lock_t lock;
and the process runs:
lock_t lock;
while(true)
{
while(lock)
{
/* wait for access to critical */
}
lock = true;
// critical stuff
lock = false;
// non-critical
}
The issues is that:
- It's voluntary (not all programs will have this form of using the lock).
- It fails condition (3), since it's still busy-waiting.
- It's totally broken...
... wait what it's broken? We'll if we get an interrupt right before the lock=true;
line then you'll get the thread in the lock as well as the interrupted thread immediately going into the lock stepping into the critical section.
Let's do a new solution:
5. Strict Alternation
For two processes A
and B
this idea extends (since you can pair programs to find the winner, then pairs of winner for an ultimate winner, and so on). Say they are:
// A
for(;;)
{
while(turn == 1)
{
/* spin */
}
// critical
turn = 1;
// noncritical
}
// B
for(;;)
{
while(turn == 0)
{
/* spin */
}
// critical
turn = 0;
// noncritical
}
I assert that this works! But why? It's because you're giving away control, not taking it. In the wrong example, the control is given to within the program (see there's lock=...
in two spots in the program). In the nice example, you are being generous and giving that control to the other programs.
Some disadvantages is that it is still busy-waiting (however, for software solutions that will be a given). The other issue is that one process cannot go until the other process has taken their turn. Each process has to go eventually. You have to go on the rollercoaster ride of the critical section.
- Peterson's Solution: (see below)
- This one is an alternative that doesn't require alternation.
int turn;
bool interested[2];
void enter(int self)
{
int other;
other = 1 - self; // if self is 0 then other is 1, and vice versa
interested[self] = true;
turn = self;
while((term == self) && interested[other]); // wait for turn
// enter the process (all others block)
}
void exit_region(int self)
{
interested[self] = false;
}
Think about an example of going through, especially if both set turn
at the same time.
- Both try to set
turn
and thus their owninterest
turn
is one of the written values- The thread that got it's value in
turn
breaks from thewhile
and thus runs - The other thread has to wait in the
while
and thus blocks.