Semaphore Programming
การจัดจังหวะการทำงานของเทรด (Thread Synchronization)
ความหมายพื้นฐานของคำว่า semaphores คือระบบการให้สัญญาณด้วยการถือธงในท่าต่างๆ ที่เราจะพบเห็นได้ของเจ้าหน้าที่ประจำสถานีรถไฟ (พนักงานชานชลา) เพื่อใช้ในการส่งสัญญาณบอกคนขับรถไฟ ว่าจะสามารถขับรถไฟแต่ละขบวนในการเข้าใช้รางรถไฟนั้นได้หรือไม่ ตัวอย่างเช่น
เมื่อพนักงานชานชลาลดธงสัญญาณต่ำลง จะเป็นการส่งสัญญาณบอกคนขับรถไฟว่าสามารถนำขบวนเข้ามาในรางนี้ได้
แต่ถ้าพนักงานชานชลายกธงสัญญาณขึ้น จะห้ามรถไฟขบวนนั้นเข้ามาในรางนี้

ต่อมาในปี ค.ศ. 1965 ก็ได้มีผู้เชี่ยวชาญทางด้านวิทยาศาสตร์คอมพิวเตอร์ Mr. Edsger Dijkstra ได้นำแนวคิดของ semaphores มาช่วยแก้ไขปัญหาของการประสานงานของกระบวนการ (process synchronization) เนื่องจากภายในระบบปฏิบัติการจะมีโปรเซสเกิดขึ้นอยู่ตลอดเวลาและเกิดขึ้นมากน้อยอยู่เสมอแต่ด้วยข้อจำกัดของทรัพยากรของระบบที่มีจำกัดทำให้ระบบปฏิบัติการจะต้องคอยจัดสรรและควบคุมให้โปรเซสต่างๆ ที่ต้องการเข้าใช้ทรัพยากรภายในระบบสามารถได้ใช้อย่างต่อเนื่องและทั่วถึงให้มากที่สุด
แต่ในบางครั้งโปรเซสที่กำลังทำงานอยู่นั้นอาจกำลังใช้ข้อมูลร่วมกันอยู่ ดังนั้นระบบปฏิบัติการหรือโปรแกรมที่เขียนขึ้นมาจึงจำเป็นจะต้องมีการจัดจังหวะของการทำงานของแต่ละโปรเซสให้เป็นระเบียบและสามารถดำเนินไปอย่างถูกต้อง
โดยนาย Dijkstra ได้เสนอแนวคิดโดยการกำหนดตัวแปรแบบจำนวนเต็ม (integer) ที่เรียกว่า semaphore S
ซึ่งสามารถกำหนดค่าเริ่มต้นได้และใช้งานได้โดยผ่านคำสั่ง 2 คำสั่งเท่านั้น คือ down (wait) และ up (signal) โดยคำสั่งทั้งสองนี้มีนิยามดั้งเดิมดังนี้
void wait(S) {
while S<=0; //do no-op;
S--;
}
void signal(S) {
S++;
}
โดยคำสั่ง down
(หรือ wait()
) นั้นจะทำการลดค่าที่ละหนึ่ง และคำสั่ง up (หรือ signal) ก็จะเป็นการเพิ่มค่าที่ละหนึ่งเช่นเดียวกัน เมื่อโปรเซสใดก็ตามที่เรียกคำสั่ง wait()
แล้วค่า S
ณ ตอนนั้นมีค่าน้อยกว่าหรือเท่ากับศูนย์ โปรเซสเหล่านั้นก็จะต้องถูกบล็อคให้รอจนกว่าโปรเซสที่เข้าในเขตวิกฤตจะออกมาแล้วทำการเพิ่มค่า S
โดยเรียกคำสั่ง signal()
เพื่อให้เข้าใจได้ง่ายขึ้นเกี่ยวกับ semaphore สามารถเปรียบเทียบได้กับตะกร้าที่มีลูกบอล (S
) จำนวนหนึ่งอยู่ โดยเมื่อมีโปรเซสใดต้องการขอเข้าถึงข้อมูลส่วนกลางในเขตวิกฤตจะต้องหยิบลูกบอลหนึ่งลูกจากตะกร้าโดยเมื่อใดที่โปรเซสนั้นเข้าถึงข้อมูลเสร็จสิ้นแล้ว ก็จะออกมาจากเขตวิกฤติแล้วคืนลูกบอลนั้นกลับไปในตะกร้าเช่นเดิม

โดยทั่วไปจะไม่ได้กำหนดว่าค่า semaphores จะต้องไม่เกินเท่าไหร่ แต่จะขึ้นอยู่กับแพลตฟอร์มนั้นๆหรือขึ้นอยู่กับการกำหนดการใช้งานของผู้ใช้เอง ซึ่งการใช้แนวคิดการนับของ semaphores เปรียบได้กับยามคอยเฝ้าดูแลการเข้าใช้งานทรัพยากรส่วนกลางของระบบ เพราะเมื่อมีโปรเซสตัวหนึ่งในฐานะผู้ผลิต (producer) กำลังบันทึกข้อมูลลงในอาเรย์ที่ถูกใช้งานร่วมกัน และมีโปรเซสอื่นที่เข้ามาอ่านข้อมูลออกจากอาเรย์นี้ในฐานะผู้ใช้ (consumer) ดังนั้นเมื่อ producer ได้เขียนข้อมูลลงไปแล้วก็จะเรียก signal เพื่อส่งสัญญาณแจ้งไปยัง consumer ว่าให้เข้ามาอ่านไปได้เลย โดย consumer ก็จะเรียก wait เพื่อเข้าถึงข้อมูลต่อไป ด้วยวิธีการ signal นี้จะทำให้ไม่มี consumer ไหนเข้าถึงข้อมูลโดยที่ภายในอาเรย์ยังไม่ได้มีข้อมูลอยู่
ระบบที่รองรับ semaphore นั้นมีทั้ง System V และ POSIX ซึ่ง POSIX semaphore นั้นถือว่าเป็นตัวที่พัฒนาตามหลังจาก System V semaphore ที่มีอยู่กับระบบปฏิบัติการ UNIX มานาน รวมทั้งอาจจะมีความยุ่งยากในการเรียกใช้งาน ทำให้ POSIX semaphore ได้ถูกพัฒนาให้ดีขึ้นและแก้ไขจุดเสียของ System V semaphore จนทำให้มีขนาดเล็กกระทัดรัด การรียกใช้งานง่าย และมีความทันสมัยรองรับสถาปัตยกรรมใหม่ๆได้ดีกว่ามาก
เมื่ออ้างถึงการใช้ mutex และ semaphore นั้นจะมีส่วนที่แตกต่างกันกล่าวคือ mutex จะสามารถถูกนำไปใช้ได้สำหรับหลายเทรดที่อยู่ภายในโปเซสเดียวกันเท่านั้น แต่ในขณะที่ POSIX semaphore สามารถถูกนำไปใช้ได้ทั้งระหว่างเทรดภายในโปรเซสเดียวกัน และสำหรับการสื่อสารระหว่างโปรเซสได้ด้วยเช่นกัน โดย POSIX semaphore นี้จะมีการกำหนดชื่อ (named) ก็ได้ หรือไม่กำหนดชื่อ (unnamed) ก็ได้ (เฉพาะในกรณีที่เป็นโปรเซสลูกกับโปรแกรมแม่) เช่นเดียวกันกับการใช้ pipes
แสดงตัวอย่างการควบคุม thread จำนวน 2 ตัวให้ไม่มีการแย่งกันเข้าใช้เขตวิกฤติพร้อมกัน
// sem1.c
#include <unistd.h> /* Symbolic Constants */
#include <sys/types.h> /* Primitive System Data Types */
#include <errno.h> /* Errors */
#include <stdio.h> /* Input/Output */
#include <stdlib.h> /* General Utilities */
#include <pthread.h> /* POSIX Threads */
#include <string.h> /* String handling */
#include <semaphore.h> /* Semaphore */
/* prototype for thread routine */
void handler(void *ptr);
/* global vars */
/* semaphores are declared global so they can be accessed
in main() and in thread routine,
here, the semaphore is used as a mutex */
sem_t mutex;
int counter; /* shared variable */
int main() {
int i[2];
pthread_t thread_a;
pthread_t thread_b;
i[0] = 0; /* argument to threads */
i[1] = 1;
sem_init(&mutex, 0, 1); /* initialize mutex to 1 - binary semaphore */
/* second param = 0 - semaphore is local */
/* Note: you can check if thread has been successfully created by checking return value of pthread_create */
pthread_create(&thread_a, NULL, (void*) &handler, (void*) &i[0]);
pthread_create(&thread_b, NULL, (void*) &handler, (void*) &i[1]);
pthread_join(thread_a, NULL);
pthread_join(thread_b, NULL);
sem_destroy(&mutex); /* destroy semaphore */
/* exit */
exit(0);
} /* main() */
void handler(void *ptr) {
int x;
x = *((int*) ptr);
printf("Thread %d: Waiting to enter critical region...\n", x);
sem_wait(&mutex); /* down semaphore */
/* START CRITICAL REGION */
printf("Thread %d: Now in critical region...\n", x);
printf("Thread %d: Counter Value: %d\n", x, counter);
printf("Thread %d: Incrementing Counter...\n", x);
counter++;
printf("Thread %d: New Counter Value: %d\n", x, counter);
printf("Thread %d: Exiting critical region...\n", x);
/* END CRITICAL REGION */
sem_post(&mutex); /* up semaphore */
pthread_exit(0); /* exit thread */
}
$ gcc -o sem1 sem1.c -lpthread
$ ./sem1
Thread 1: Waiting to enter critical region...
Thread 1: Now in critical region...
Thread 1: Counter Value: 0
Thread 1: Incrementing Counter...
Thread 1: New Counter Value: 1
Thread 1: Exiting critical region...
Thread 0: Waiting to enter critical region...
Thread 0: Now in critical region...
Thread 0: Counter Value: 1
Thread 0: Incrementing Counter...
Thread 0: New Counter Value: 2
Thread 0: Exiting critical region...
แสดงตัวอย่างการสร้าง thread จำนวน 3 ตัว โดยแต่ละตัวจะเข้าในเขตวิกฤติจำนวน 3 รอบ ซึ่งแต่ละรอบจะทำการสุ่มเวลา (time) แต่ไม่เกิน 30 วินาทีเพื่อทำการหยุดรอด้วยคำสั่ง sleep(time) ตามเวลาที่ได้สุ่มออกมา โดยในโปรแกรมจะกำหนดค่า semaphore เท่ากับ 2 เพื่ออนุญาตให้ thread สามารถเข้ามาในเขตวิกฤติในเวลาเดียวกันได้มากสุดเพียง 2 ตัวเท่านั้น
// sem2.c
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void* doSomething1();
void* doSomething2();
void* doSomething3();
sem_t sem;
int main() {
// initialize semaphore to 2
sem_init(&sem, 1, 2);
pthread_t thread1, thread2, thread3;
pthread_create(&thread1, NULL, &doSomething1, NULL);
pthread_create(&thread2, NULL, &doSomething2, NULL);
pthread_create(&thread3, NULL, &doSomething3, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);
return 0;
}
void doSomething(char c) {
int i, time;
for (i = 0; i < 3; i++) {
// P operation
if (sem_wait(&sem) == 0) {
// generate random amount of time (< 30 seconds)
time = (int) ((double) rand() / RAND_MAX * 30);
printf("Thread %c enters and sleeps for %d seconds...\n", c, time);
sleep(time);
printf("Thread %c leaves the critical section\n", c);
// V operation
sem_post(&sem);
}
}
}
void* doSomething1() {
// thread A
doSomething('A');
return 0;
}
void* doSomething2() {
// thread B
doSomething('B');
return 0;
}
void* doSomething3() {
// thread C
doSomething('C');
return 0;
}
$ gcc -o sem2 sem2.c -lpthread -lrt
$ ./sem2
Thread C enters and sleeps for 25 seconds...
Thread B enters and sleeps for 11 seconds...
Thread B leaves the critical section
Thread B enters and sleeps for 23 seconds...
Thread C leaves the critical section
Thread C enters and sleeps for 23 seconds...
Thread B leaves the critical section
Thread B enters and sleeps for 27 seconds...
Thread C leaves the critical section
Thread C enters and sleeps for 5 seconds...
Thread C leaves the critical section
Thread A enters and sleeps for 10 seconds...
Thread B leaves the critical section
Thread A leaves the critical section
Thread A enters and sleeps for 23 seconds...
Thread A leaves the critical section
Thread A enters and sleeps for 8 seconds...
Thread A leaves the critical section
แสดงตัวอย่างการจำลองสถานการณ์การลงคะแนนเลือกตั้ง (voting) โดยสร้างเทรดจำนวน 8 เทรดแทนผู้มาใช้สิทธิ์เลือกตั้งที่กำลังรอเข้าไปกากบาท ตามจำนวนตู้เลือกตั้ง 3 ตู้ (booth) ดังนั้นจะต้องกำหนดให้ค่า semaphore มีค่าเท่ากับ 3 ซึ่งหมายถึงการอนุญาตให้เทรด (ผู้มาใช้สิทธิ์เลือกตั้ง) เข้าไปกากบาทเลือกที่ตู้เลือกตั้งที่เตรียมไว้ให้ 3 ตู้ได้
// sem3.c
#include <semaphore.h>
#include <pthread.h>
#include <errno.h>
#define NUM_BOOTHS 3
#define NUM_THREADS 8
typedef struct {
pthread_t id;
int thread_number;
} thread_data;
/*
* The voting booth semaphore.
*/
sem_t semaphore;
void do_vote(void) {
sleep(2); /* It takes time to vote. */
}
/*
* The voter thread body.
*/
void* voter_thread(void *p) {
thread_data *td = p;
struct timespec abs_time;
abs_time.tv_sec = time(NULL) + 3;
abs_time.tv_nsec = 0;
printf("%d checking for a booth.\n", td->thread_number);
if (0 == sem_timedwait(&semaphore, &abs_time)) {
printf("%d entering booth.\n", td->thread_number);
do_vote();
printf("%d done voting.\n", td->thread_number);
sem_post(&semaphore);
} else {
printf("%d tired of waiting.\n", td->thread_number);
}
return 0;
}
int main(void) {
int i;
thread_data data[NUM_THREADS];
/* This function must always be called before using a semaphore. */
sem_init(&semaphore, 0, NUM_BOOTHS);
/* Start the threads. */
for (i = 0; i < NUM_THREADS; i++) {
data[i].thread_number = i;
if (pthread_create(&data[i].id, 0, voter_thread, &data[i]) != 0) {
printf("Cannot create thread: %d.\n", errno);
}
}
/* Wait for the threads to finish. */
for (i = 0; i < NUM_THREADS; i++) {
if (0 != pthread_join(data[i].id, 0)) {
printf("Cannot join thread %d: %d.\n", i, errno);
}
}
printf("Done.\n");
sem_destroy(&semaphore);
sleep(1);
return 0;
}
$ gcc -o sem3 sem3.c -lpthread
$ ./sem3
6 checking for a booth.
6 entering booth.
5 checking for a booth.
5 entering booth.
7 checking for a booth.
7 entering booth.
4 checking for a booth.
3 checking for a booth.
2 checking for a booth.
1 checking for a booth.
0 checking for a booth.
7 done voting.
5 done voting.
6 done voting.
4 entering booth.
3 entering booth.
2 entering booth.
1 tired of waiting.
0 tired of waiting.
4 done voting.
3 done voting.
2 done voting.
Done.
แสดงตัวอย่างเมื่อแต่ละโปรเซสมีการใช้ shared memory ร่วมกัน ซึ่งตัว semaphore เองไม่ได้ทำงานข้ามโปรเซสแต่จะทำงานระหว่างเทรดภายในโปรเซสเดียวกัน
// semex.c
/* A program derived from Robbins and Robbins ch12, ch14, and ch15 examples */
/* See http://vip.cs.utsa.edu/usp/programs.html and R&R book */
/* Shows use of POSIX semaphores to provide mutex between child processes */
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/stat.h>
/* Without use of shared memory, the semaphore doesn't work across
processes! Of course it would work with threads in one process,
since they share the process address space. */
/* uncomment the next line to start using shared memory for sem_t object */
/*#define USE_SHARED_MEMORY */
#define MAXPIDS 10
void worker(int i, void *args);
int main(int argc, char *argv[]) {
int error;
int i;
int id, n;
sem_t *semlock;
pid_t pids[MAXPIDS];
if (argc != 2) { /* check for valid number of command-line arguments */
fprintf(stderr, "Usage: %s numprocesses\n", argv[0]);
return 1;
}
n = atoi(argv[1]);
if (n > MAXPIDS)
return 1;
#ifdef USE_SHARED_MEMORY
if ((id = shmget(IPC_PRIVATE, sizeof(sem_t), (S_IRUSR|S_IWUSR))) == -1) {
perror("Failed to create shared memory segment");
return 1;
}
if ((semlock = (sem_t *)shmat(id, NULL, 0)) == (void *)-1) {
perror("Failed to attach memory segment");
return 1;
}
#else
/* try using ordinary process-private memory for sem_t variable */
semlock = (sem_t*) malloc(sizeof(sem_t));
#endif
if (sem_init(semlock, 1/*shared across processes*/, 1) == -1) {
perror("Failed to initialize semaphore");
} else {
for (i = 0; i < n; i++) {
if ((pids[i] = fork()) < 0) {
fprintf(stderr, "Failed to create process:%s\n",
strerror(error));
return 1;
}
if (pids[i] == 0) { /* child */
worker(i, semlock);
exit(0);
}
}
/* here in parent of all */
for (i = 0; i < n; i++)
wait(0);
fprintf(stderr, "workers all done\n");
}
#ifdef USE_SHARED_MEMORY
if (shmdt((void *)semlock) == -1) { /* shared memory detach */
perror("Failed to destroy shared memory segment");
return 1;
}
#else
free(semlock);
#endif
return 0;
}
// semexworker.c
#include <errno.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#define TEN_MILLION 10000000L
#define BUFSIZE 1024
void worker(int i, void *args) {
char buffer[BUFSIZE];
char *c;
sem_t *semlockp;
struct timespec sleeptime;
semlockp = (sem_t*) args;
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = TEN_MILLION;
snprintf(buffer, BUFSIZE, "This is process %ld\n", (long) getpid());
c = buffer;
setbuf(stderr, NULL); // specify no buffering for stderr
/****************** entry section *******************************/
while (sem_wait(semlockp) == -1) /* Entry section */
if (errno != EINTR) {
fprintf(stderr, "Thread failed to lock semaphore\n");
return;
}
/****************** start of critical section *******************/
while (*c != '\0') {
fputc(*c, stderr); // no buffering: output each synchronously
c++;
nanosleep(&sleeptime, NULL);
}
/****************** exit section ********************************/
if (sem_post(semlockp) == -1) /* Exit section */
fprintf(stderr, "Thread failed to unlock semaphore\n");
/****************** remainder section ***************************/
return;
}
ทำการคอมไพล์โปรแกรม โดยไม่ได้ใช้ semaphore ในการควบคุมการเข้าถึง shared memory ซึ่งผลการทำงานของโปรแกรมจะเห็นว่าทั้ง 4 โปรเซส ต่างแย่งการเข้าใช้พื้นที่ร่วมในเวลาเดียวกัน ทำให้ค่าภายใน shared memory แสดงทับซ้อนกันดังแสดงข้างล่าง
$ gcc -o semex semex.c semexworker.c -lrt -lpthread
$ ./semex
Usage: ./semex numprocesses
$ ./semex 4
TTTThhhhiiiissss iiiissss pppprrrroooocccceeeessssssss 5555555522228976
workers all done
เพิ่มเทคนิคของ semaphore เข้าไปโดยการคอมไพล์โปรแกรมใหม่อีกครั้ง และกำหนด (define) ให้ใช้ USE_SHARED_MEMORY เมื่อรันโปรแกรมอีกครั้งจะสังเกตเห็นว่า semaphore สามารถใช้ในการควบคุมจังหวะการเข้าใช้พื้นที่ shared memory ได้ทีละโปรเซส ดังแสดงข้างล่าง
$ gcc -o semex semex.c semexworker.c -lrt -lpthread -DUSE_SHARED_MEMORY
$ ./semex 4
This is process 5595
This is process 5594
This is process 5593
This is process 5592
workers all done
จากตัวอย่างข้างต้นจะเห็นว่าสามารถใช้ semaphore เพื่อช่วยในการจัดการจังหวะการเข้าใช้งาน shared memory ของแต่ละโปรเซสที่เกิดขึ้นจากคำสั่ง fork() ได้ ดังนั้นจะเห็นว่าข้อดีของ semaphores บนกลไกของการจัดการจังหวะการทำงานของโปรเซสต่างๆที่พยายามเข้าถึงทรัพยากรกลาง ซึ่งสามารถนำไปใช้ได้ทั้งกลุ่มโปรเซสที่เกี่ยวข้องกัน (related processes) เช่น ระหว่างโปรเซสแม่และโปรเซสลูกที่ใช้คำสั่ง fork() และกลุ่มโปรเซสที่ไม่เกี่ยวข้องกัน (unrelated processes)
แสดงตัวอย่างของเข้าถึงทรัพยากรกลางของโปรเซสที่เกี่ยวข้องกัน (related processes) โดยโปรเซสแม่จะทำการสร้างโปรเซสลูกด้วยคำสั่ง fork() ดังตัวอย่างข้างล่าง
// sem5.c
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char **argv) {
int fd, i, count = 0, nloop = 10, zero = 0, *ptr;
sem_t mutex;
//open a file and map it into memory
fd = open("log.txt", O_RDWR | O_CREAT, S_IRWXU);
write(fd, &zero, sizeof(int));
ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
/* create, initialize semaphore */
if (sem_init(&mutex, 1, 1) < 0) {
perror("semaphore initialization");
exit(0);
}
if (fork() == 0) { /* child process*/
for (i = 0; i < nloop; i++) {
sem_wait(&mutex);
printf("child entered critical section: %d\n", (*ptr)++);
sleep(2);
printf("child leaving critical section\n");
sem_post(&mutex);
sleep(1);
}
exit(0);
}
/* back to parent process */
for (i = 0; i < nloop; i++) {
sem_wait(&mutex);
printf("parent entered critical section: %d\n", (*ptr)++);
sleep(2);
printf("parent leaving critical section\n");
sem_post(&mutex);
sleep(1);
}
exit(0);
}
$ gcc -o sem5 sem5.c -lpthread
$ ./sem5
parent entered critical section: 0
child entered critical section: 1
parent leaving critical section
child leaving critical section
parent entered critical section: 2
child entered critical section: 3
parent leaving critical section
child leaving critical section
parent entered critical section: 4
child entered critical section: 5
parent leaving critical section
child leaving critical section
จากตัวอย่างข้างต้นจะมีส่วนของหน่วยความจำ (ptr) ที่เชื่อมอยู่กับไฟล์ log.txt ซึ่งทั้งโปรเซสแม่และโปรเซสลูกจะต้องเข้าใช้งานร่วมกัน
แสดงตัวอย่างการจัดจังหวะการเข้าใช้งานทรัพยากรระหว่างสองโปรเซสที่ไม่ได้มีความเกี่ยวข้องกัน (unrelated processes) ซึ่งเป็นโปรแกรมที่แยกออกจากอย่างชัดเจน ดังตัวอย่างข้างล่าง
// sem_server.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[] = "vik";
int main() {
char ch;
int shmid;
key_t key;
char *shm, *s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize semaphore
mutex = sem_open(SEM_NAME, O_CREAT, 0644, 1);
if (mutex == SEM_FAILED) {
perror("unable to create semaphore");
sem_unlink(SEM_NAME);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key, SHMSZ, IPC_CREAT | 0666);
if (shmid < 0) {
perror("failure in shmget");
exit(-1);
}
//attach this segment to virtual memory
shm = shmat(shmid, NULL, 0);
//start writing into memory
s = shm;
for (ch = 'A'; ch <= 'Z'; ch++) {
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
//the below loop could be replaced by binary semaphore
while (*shm != '*') {
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
_exit(0);
}
// sem_client.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[] = "vik";
int main() {
char ch;
int shmid;
key_t key;
char *shm, *s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize existing semaphore
mutex = sem_open(SEM_NAME, 0, 0644, 0);
if (mutex == SEM_FAILED) {
perror("reader:unable to execute semaphore");
sem_close(mutex);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key, SHMSZ, 0666);
if (shmid < 0) {
perror("reader:failure in shmget");
exit(-1);
}
//attach this segment to virtual memory
shm = shmat(shmid, NULL, 0);
//start reading
s = shm;
for (s = shm; *s != NULL; s++) {
sem_wait(mutex);
putchar(*s);
sem_post(mutex);
}
//once done signal exiting of reader:This can be replaced by another semaphore
*shm = '*';
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
exit(0);
}
ให้เปิดหน้าต่าง terminal ขึ้นมาแล้วทำการคอมไพล์และเรียกใช้โปรแกรม sem_server เพื่อให้โปรเซสทำการเข้าไปขอเปิดใช้หน่วยความจำและกำหนดกุญแจการเข้าถึงข้อมูลภายใน
$ gcc -o sem_server sem_server.c -lpthread
$ ./sem_server
จากตัวอย่างโปรแกรมข้างต้น ตัวโปรแกรม sem_server และ sem_client นั้นจะถูกเรียกขึ้นมาแยกออกจากกัน ซึ่งจะมีการใช้ semaphore ในการควบคุมการจัดจังหวะการเข้าใช้ทรัพยากร โดยปกติเมื่อมีการใช้ mutex จะอนุญาตให้โปรเซสเข้าถึงทรัพยากรแบบตามลำดับ (serial access) แตกต่างจากการใช้ semaphore ที่สามารถให้ โปรเซสเข้าถึงทรัพยากรได้แบบคู่ขนานกันได้ (parallel access)
Last updated
Was this helpful?