lock을 사용하는 이유 → Mutual exclusion
spinlock의 단점
typedef struct {
int value; // number of task that could access to CS
struct process *Q; // task that currently wait for CS
} semaphore;
void wait(semaphore *S) {
S->value--;
if (S->value < 0) { // if already
add this process to S->Q; // add to waiting queue
block(); // sleep(pass to OS?)
}
}
void signal(semaphore *S) {
S->value++;
if (S->value <= 0) { // if waiting queue is exist...
remove a process P from S->Q;
wakeup(P); // wake OS
}
}
S->value--
). if (S->value < 0)
: CS에 들어간 task가 꽉 차버렸기 때문에 일단 대기한다.S->value++
). if (S->value <= 0)
: 대기열이 존재하므로, queue에서 한명을 빼서 해당 task를 깨운다.N == 1
semaphore 영역에 들어갈 수 있는 thread가 단 1개. mutex와 동작이 거의 다를 것이 없다.
N > 1
semaphore 영역에 들어갈 수 있는 thread가 여러개인 경우. 이 경우에는 CV와 동작이 비슷하다.
void produce(data)
{
while (count==N); // wait until buffer have empty cell
buffer[in] = data;
in = (in+1) % N;
count++;
}
void consume(&data)
{
while (count==0); // wait until buffer have object
*data = buffer[out];
out = (out+1) % N;
count--;
}
Semaphore mutex = 1; // mutex for CS(access to buffer)
empty = N; // trigger producer(is buffer full?)
full = 0; // trigger consumer(is buffer empty?)
void produce(data)
{
wait(&empty); // check buffer empty
wait(&mutex); // enter to CS
buffer[in] = data;
in = (in+1) % N;
signal(&mutex);
signal(&full); // signal full(buffer is no more empty)
}
void consume(&data)
{
wait(&full); // wait until buffer is not empty
wait(&mutex); // enter to CS
*data = buffer[out];
out = (out+1) % N;
signal(&mutex);
signal(&empty); // signal empty(buffer is no more full)
}
Reader와 Writer들(threads) 사이에 공유되는 resource가 있다고 하자
Implementation with semaphores
// number of readers
int readcount = 0;
// mutex for readcount
Semaphore mutex = 1;
// mutex for reading/writing
Semaphore rw = 1;
void Writer() {
wait(&rw); // nothing special... only one writer could access to resource
...
// Write
...
signal(&rw);
}
void Reader() {
wait(&mutex); // semaphore를 이용해서 readcount라는 CS에 대한 lock을 구현하고 있는 것
readcount++;
if (readcount == 1) // 현재 reading중인 reader가 나만이라면...
wait(&rw);
signal(&mutex);
...
// Read
...
wait(&mutex); // 마찬가지의 역할을 수행하고 있음
readcount--;
if (readcount == 0) // 현재 더이상 read하려는 reader가 없는 경우
signal(&rw);
signal(&mutex);
}
Writer()
는 전혀 새로울 것이 없다.
Reader()
의 구현은 Writer()
에 비해서 흥미로운 문제를 하나 가지고 있다.
문제점: starvation → 한번 reading이 시작되면 writer의 차례가 돌아오기 위해서는 모든 reader가 reading을 멈추는 경우에만 가능하다!
// semaphore for each fork for
// initialized to 1
Semaphore forks[N];
#define L(i) (i)
#define R(i) ((i + 1) % N)
void philosopher(int i)
{
while (1) {
think();
pickup(i);
eat();
putdown(i);
}
}
// get fork pair lock
void pickup(int i) {
wait(&forks[L(i)]);
wait(&forks[R(i)]);
}
// release fork pair lock
void putdown(int i) {
signal(&forks[L(i)]);
signal(&forks[R(i)]);
}
// semaphore for each fork for
// initialized to 1
Semaphore forks[N];
#define L(i) (i)
#define R(i) ((i + 1) % N)
void philosopher(int i)
{
while (1) {
think();
pickup(i);
eat();
putdown(i);
}
}
// get fork pair lock
void pickup(int i) {
if (i == (N-1)) {
wait(&forks[R(i)]);
wait(&forks[L(i)]);
} else {
wait(&forks[L(i)]);
wait(&forks[R(i)]);
}
}
// release fork pair lock
void putdown(int i) {
signal(&forks[L(i)]);
signal(&forks[R(i)]);
}
typedef struct semaphore {
unsigned int val;
struct spinlock lock;
void * thread[NPROC];
unsigned int next;
unsigned int end;
} semaphore;
void sem_init(struct semaphore * s, uint i) {
int j;
s->val = i;
initlock(&s->lock, (char*)s);
for (j = 0; j < NPROC; j++)
s->thread[j] = 0;
s->next = s->end = 0;
}
void sem_wait(struct semaphore * s) {
acquire(&s->lock);
while (s->val == 0) {
s->thread[s->end] = proc;
s->end = (s->end + 1) % NPROC;
sleep(proc, &s->lock);
}
s->val = s->val - 1;
release(&s->lock);
}
void sem_signal(struct semaphore * s) {
acquire(&s->lock);
s->val = s->val + 1;
if (s->thread[s->next]) {
wakeup(s->thread[s->next]);
s->thread[s->next] = 0;
s->next = (s->next + 1) % NPROC;
}
release(&s->lock);
}