Sunteți pe pagina 1din 14

Unit -2

Topic: - Threads, Critical section problem, Semaphores, Bounded buffer problem, Readers –
Writers problem, Dinning Philosopher problem

Processes & Threads

Process Creation

There are two main models of process creation - the fork/exec and the spawn models. On systems that
support fork, a new process is created as a copy of the original one and then explicitly executes (exec) a
new program to run. In the spawn model the new program and arguments are named in the system call, a
new process is created and that program run directly.

Fork is the more flexible model. It allows a program to arbitrarily change the environment of the child
process before starting the new program. Typical fork pseudo-code looks like:

if ( fork() == 0 ) {

/* Child process */

change standard input

block signals for timers

run the new program

else {

/* Parent process */

wait for child to complete

• Any parameters of the child process’s operating environment that must be changed must be
included in the parameters to spawn, and spawn will have a standard way of handling them. There
are various ways to handle the proliferation of parameters that results, for example AmigaDOS®
uses tag lists - linked lists of self-describing parameters - to solve the problem.
• The steps to process creation are similar for both models. The OS gains control after the fork or
spawn system call, and creates and fills a new PCB. Then a new address space (memory) is
allocated for the process. Fork creates a copy of the parent address space, and spawn creates a new
address space derived from the program. Then the PCB is put on the run list and the system call
returns.
• An important difference between the two systems is that the fork call must create a copy of the
parent address space. This can be wasteful if that address space will be deleted and rewritten in a
few instruction’s time. One solution to this problem has been a second system call, vfork, that lets
the child process use the parent’s memory until an exec is made. We’ll discuss other systems to
mitigate the cost of fork when we talk about memory management. Which is “better” is an open
issue. The tradeoffs are flexibility vs. overhead, as usual.

Threads

• Threads are lightweight processes. They improve performance by weakening the process
abstraction
• A process is one thread of control executing one program in one address space. A thread may have
multiple threads of control running different parts of a program in one address space. Because
threads expose multitasking to the user (cheaply) they are more powerful, but more complicated.
Thread programmers have to explicitly address multithreading and synchronization (which is the
topic of our next unit).

User Threads

• User threads are implemented entriely in user space. The programmer of the thread library writes
code to synchronize threads and to context switch them, and they all run in one process. The
operating system is unaware that a thread system is even running.
• User-level threads replicate some amount of kernel level functionality in user space. Examples of
user-level threads systems are Nachos and Java (on OSes that don’t support kernel threads).
Becuase the OS treats the running process like any other there is no additional kernel overhead for
user-level threads. However, the user-level threads only run when the OS has scheduled their
underlying process (making a blocking system call blocks all the threads.)

Kernel Threads

Some OS kernels support the notion of threads and schedule them directly. There are system calls to
create threads and manipulate them in ways similar to processes. Synchronization and scheduling may be
provided by the kernel. Kernel-level threads have more overhead in the kernel (a kernel thread control
block) and more overhead in their use (manipulating them requires a system call). However the
abstraction is cleaner (threads can make system calls independently).

Race Conditions

An atomic action is an indivisible action - an action taken by a process than cannot be interrputed by a
context switch. When accesses to a shared variable are not atomic, call it a race condition. Consider the
following simple example:

shared int x =3;

process_1 () {

x = x +1;

print x;

process_2 () {
x = x +1;

print x;

Note that although x = x +1 looks like one statement, it probably compiles into at least 2 and

probably three machine instructions. A likely translation is:

LOAD r1 x

ADD r1+1 r1

STORE r1 x

Because of this, depending on when context switches occur, any of the following sets of output and final
values in x are possible:

1 This is called multiprogramming or multitasking. Multiprocessing refers to the contition in which many
CPUs, each running a separate program have access to the same memory and peripherals. Multiprocessing
synchronization is a related, but slightly harder problem.

Process 1 Process 2 variable

455

545

444

If the program is keeping bank balances, someone’s deposit just got lost. If it’s controlling chemotherapy,
maybe their life.

Only code that is manipulating shared data needs to worry about them. The most common way to avoid
race conditions is to mark the sections of code that are accessing shared data (such parts are called critical
sections or critical regions) and insure that only one process is ever executing code in the critical section
or context switched out of code in the critical section.

This property is called mutual exclusion.

Critical sections are specific to shared data. If a and b are shared variables that are unrelated to each
other, one process can be in a critical section that accesses variable a while another process is in a critical
section that accesses b. Related variables must share mutual exclusion. If a program invariant is that two
varibles add to a constant, they must share mutual exclusion. For simplicity, We will talk about ways to
enforce mutual exclusion as though there is only one set of related shared variables. We’ll define 2
functions, enter_region and leave_region that guarantee that at most one process has returned from
enter_region and not leave_region, with the assumption being that a process will call enter_region before
entering the critical region and leave_region immediately after leaving it.
Mutual Exclusion (Simple & Wrong)

int flag;

void enter_region(int process) {

while ( flag == 1 );

flag = 1;

void leave_region(int process) {

flag = 0;

That code’s wrong, and understanding why it’s wrong will start you on your way to understanding mutual
exclusion. The code fails because flag is as much a shared variable as anything in the critical section. The
same way that two processes increment x in and leave it with incremented once from the point of view of
an external observer in our first example, two processes can both see the flag as zero before it’s set to one,
and both enter the critical section.

The Critical-Section Problem

N processes all competing to use shared data.

Structure of process Pi ----Each process has a code segment, called the critical section, in which the shared
data is accessed.

repeat

entry section /* enter critical section */

critical section /* access shared variables */

exit section /* leave critical section */

remainder section /* do other work */

untilfalse

Problem

Ensure that when one process is executing in its critical section, no other process is allowed to execute in
its critical section.
Solution: Critical Section Problem -Requirements

Mutual Exclusion

If process Pi is executing in its critical section, then no other processes can be executing in their critical
sections.

Progress

If no process is executing in its critical section and there exists some processes that wish to enter their
critical section, then the selection of the processes that will enter the critical section next cannot be
postponed indefinitely.

Bounded Waiting

A bound must exist on the number of times that other processes are allowed to enter their critical sections
after a process has made a request to enter its critical section and before that request is granted.

Solution: Critical Section Problem -Requirements

• Assume that each process executes at a nonzero speed.


• No assumption concerning relative speed of the n processes

Solution: Critical Section Problem --Initial Attempt

Only 2 processes, P0 and P1

General structure of process Pi (Pj)

repeat

entry section

critical section

exit section

remainder section

untilfalse

Processes may share some common variables to synchronize their actions.

Algorithm 1

*Shared Variables:
*varturn: (0..1);

initially turn= 0;

*turn= i *Pican enter its critical section

*Process Pi

repeat

while turn <> i do no-op;

critical section

turn :=j;

remainder section

untilfalse

Satisfies mutual exclusion, but not progress.

Algorithm 2

*Shared Variables

*varflag: array(0..1) ofboolean;

initially flag[0] = flag[1]= false;

*flag[i]= true *Pi ready to enter its critical section

*Process Pi

repeat

flag[i] :=true;

while flag[j] do no-op;

critical section

flag[i]:=false;

remainder section

untilfalse

Can block indefinitely…. Progress requirement not met.


Algorithm 3

*Shared Variables

*varflag: array(0..1) ofboolean;

initially flag[0] = flag[1]= false;

*flag[i]= true *Pi ready to enter its critical section

*Process Pi

repeat

while flag[j] do no-op;

flag[i] :=true;

critical section

flag[i]:=false;

remainder section

untilfalse

Does not satisfy mutual exclusion requirement ….

Algorithm 4

*Combined Shared Variables of algorithms 1 and 2

*Process Pi

repeat

flag[i] :=true;

turn:= j;

while (flag[j]and turn=j) do no-op;

critical section

flag[i]:=false;

remainder section
untilfalse

YES!!! Meets all three requirements, solves the critical section


problem for 2 processes.

Bakery Algorithm

• Critical section for n processes


• Before entering its critical section, process receives a number. Holder of the smallest number
enters critical section.
• If processes Pi and Pjreceive the same number,
• if i <= j, then P is served first; else Pjis served first.
• The numbering scheme always generates numbers in increasing order of enumeration; i.e.
1,2,3,3,3,3,4,4,5,5

Notation -

• Lexicographic order(ticket#, process id#)


• (a,b) < (c,d) if (a<c) or if ((a=c) and (b< d))
• max(a0,….an-1) is a number, k, such that k>=aifor i = 0,…,n-1
• Shared Data

varchoosing: array[0..n-1] ofboolean;(initialized to false)

number: array[0..n-1] ofinteger; (initialized to 0)

repeatchoosing[i] :=true;number[i] := max(number[0], number[1],…,number[n-1]) +1;choosing[i]


:=false;forj:= 0 ton-1do beginwhilechoosing[j] dono-op;while number[j] <> 0 and (number[j],j)<
(number[i],i)do no-op;end;critical sectionnumber[i]:=0;remainder sectionuntilfalse;

Hardware Solutions for Synchronization

• Mutual exclusion solutions presented depend on memory hardware having read/write cycle.
• If multiple reads/writes could occur to the same memory location at the same time, this would not
work.
• Processors with caches but no cache coherency cannot use the solutions
• In general, it is impossible to build mutual exclusion without a primitive that provides some form
of mutual exclusion.
• How can this be done in the hardware???
• Test and modify the content of a word atomically -Test-and-set instruction
functionTest-and-Set(vartarget: boolean): boolean;

begin

Test-and-Set:= target;

target:= true;

end;

*Similarly “SWAP” instruction

Mutual Exclusion with Test-and-Set

Shared data: varlock: boolean(initially false)

Process Pi

repeat

while Test-and-Set (lock)do no-


op;

critical section

lock :=false;

remainder section

untilfalse;

Bounded Waiting Mutual Exclusion with Test-and-Set

varj : 0..n-1;

key : boolean;

repeat

waiting [i] :=true; key:=true;

whilewaiting[i] andkeydokey := Test-and-Set(lock);

waiting [i ] := false;

critical section

j := j + 1 mod n;

while(j<> i) and(notwaiting[j]) doj:= j+ 1 modn;


ifj= ithenlock:= false;

elsewaiting[j] :=
false;

remainder section

untilfalse

SEMAPHORES

A semaphore is a higher level mechanism for controlling concurrent access to a shared resources.

Instead of using operations to read and write a shared variable, a semaphore encapsulates the necessary
shared data, and allows access only by a restricted set of operations. Because a semaphore has the power
to suspend and wake processes, semaphores and similar higher-level constructs require the co-operation of
the operating system and/or programming language run-time system. There are two operations on a
semaphore S. Worker processes can wait() or signal() a semaphore. For historical reasons, the wait and
signal operations are sometimes abbreviated as P and V respectively.

Note that with semaphores, worker processes do not execute a potentially wasteful busy-waiting loop.

SEMAPHORE OPERATIONS

Wait(): a process performs a wait operation to tell the semaphore that it wants exclusive access to the
shared resource. If the semaphore is empty, then the semaphore enters the full state and allows the
process to continue its execution immediately. If the semaphore is full, then the semaphore suspends the
process (and remembers that the process is suspended).

Signal(): a process performs a signal operation to inform the semaphore that it is finished using the shared
resource. If there are processes suspended on the semaphore, the semaphore wakes one of the up. If there
are no processes suspended on the semaphore, the semaphore goes into the empty state.

TYPES OF SEMAPHORES

The semaphore we have described is a binary semaphore. It allows only one process at a time to access the
shared resource. The textbook desribes a more general semaphore that allows N > 1 processes to access
the resource simultaneously. Instead of having states empty and full, it uses a counter S. The counter is
initialised to N, with S = 0 corresponding to the full state.

Note that different implementations of semaphores use different policies for choosing which process to
wake during a signal action. A blocked set semaphore maintains a set of waiting processes and wakes one
at random; while a blocked queue semaphore wakes processes in the order in which then were suspended.

MONITORS

Monitors can be thought of as an object-oriented extension of the idea of a semaphore. A monitor


encapsulates a set of shared variables or data and a set of operations for manipulating the data, and it
ensures mutual exclusion for each of the operations. Worker processes are thus freed from the
responsibility for ensuring mutual exclusion. Provided the encapsulation is adhered to, it becomes
impossible to interact with the shared data without correct mutual exclusion, eliminating many
possibilities for error.

In Java for instance, every object is potentially a monitor. Monitor operations are specified by putting
them in a method and marking the method with the synchronized keyword.

The Bounded Buffer Problem

Consider:

• a buffer which can store n items


• a producer process which creates the items (1 at a time)
• a consumer process which processes them (1 at a time)
• A producer cannot produce unless there is an empty buffer slot to fill.
• A consumer cannot consume unless there is at least one produced item.

Semaphore empty=N, full=0, mutex=1;

process producer {

while (true) {

empty.acquire();

mutex.acquire();

// produce

mutex.release();

full.release();

process consumer {

while (true) {

full.acquire();

mutex.acquire();

// consume

mutex.release();
empty.release();

The semaphore mutex provides mutual exclusion for access to the buffer.

The Readers-Writers Problem

• A data item such as a file is shared among several processes.


• Each process is classified as either a reader or writer.
• Multiple readers may access the file simultaneously.
• A writer must have exclusive access (i.e., cannot share with either a reader or another writer).
• A solution gives priority to either readers or writers.
o readers' priority: no reader is kept waiting unless a writer has already obtained permission
to access the database
o writers' priority: if a writer is waiting to access the database, no new readers can start
reading

A solution to either version may cause starvation

• in the readers' priority version, writers may starve


• in the writers' priority version, readers may starve

A semaphore solution to the readers' priority version (without addressing starvation):

Semaphore mutex = 1;

Semaphore db = 1;

int readerCount = 0;

process writer {

db.acquire();

// write

db.release();

process reader {

// protecting readerCount

mutex.acquire();
++readerCount;

if (readerCount == 1)

db.acquire();

mutex.release();

// read

// protecting readerCount

mutex.acquire();

--readerCount;

if (readerCount == 0)

db.release;

mutex.release();

readerCount is a <cs> over which we must maintain control and we use mutex to do so.

The Dining Philosophers Problem

n philosophers sit around a table thinking and eating. When a philosopher thinks she does not interact with
her colleagues. Periodically, a philosopher gets hungry and tries to pick up the chopstick on his left and on
his right. A philosopher may only pick up one chopstick at a time and, obviously, cannot pick up a
chopstick already in the hand of neighbor philosopher.

The dining philosophers problems is an example of a large class or concurrency control problems; it is a
simple representation of the need to allocate several resources among several processes in a deadlock-free
and starvation-free manner.

A semaphore solution:

// represent each chopstick with a semaphore

Semaphore chopstick[] = new Semaphore[5]; // all = 1 initially

process philosopher_i {

while (true) {

// pick up left chopstick


chopstick[i].acquire();

// pick up right chopstick

chopstick[(i+1) % 5].acquire();

// eat

// put down left chopstick

chopstick[i].release();

// put down right chopstick

chopstick[(i+1) % 5].release();

// think

Note :-This solution guarantees no two neighboring philosophers eat simultaneously, but has the
possibility of creating a deadlock

S-ar putea să vă placă și