Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University
โดยคำสั่ง down (หรือ wait()) นั้นจะทำการลดค่าที่ละหนึ่ง และคำสั่ง up (หรือ signal) ก็จะเป็นการเพิ่มค่าที่ละหนึ่งเช่นเดียวกัน เมื่อโปรเซสใดก็ตามที่เรียกคำสั่ง wait() แล้วค่า S ณ ตอนนั้นมีค่าน้อยกว่าหรือเท่ากับศูนย์ โปรเซสเหล่านั้นก็จะต้องถูกบล็อคให้รอจนกว่าโปรเซสที่เข้าในเขตวิกฤตจะออกมาแล้วทำการเพิ่มค่า S โดยเรียกคำสั่ง signal()
เพื่อให้เข้าใจได้ง่ายขึ้นเกี่ยวกับ semaphore สามารถเปรียบเทียบได้กับตะกร้าที่มีลูกบอล (S) จำนวนหนึ่งอยู่ โดยเมื่อมีโปรเซสใดต้องการขอเข้าถึงข้อมูลส่วนกลางในเขตวิกฤตจะต้องหยิบลูกบอลหนึ่งลูกจากตะกร้าโดยเมื่อใดที่โปรเซสนั้นเข้าถึงข้อมูลเสร็จสิ้นแล้ว ก็จะออกมาจากเขตวิกฤติแล้วคืนลูกบอลนั้นกลับไปในตะกร้าเช่นเดิม
ระบบที่รองรับ semaphore นั้นมีทั้ง System V และ POSIX ซึ่ง POSIX semaphore นั้นถือว่าเป็นตัวที่พัฒนาตามหลังจาก System V semaphore ที่มีอยู่กับระบบปฏิบัติการ UNIX มานาน รวมทั้งอาจจะมีความยุ่งยากในการเรียกใช้งาน ทำให้ POSIX semaphore ได้ถูกพัฒนาให้ดีขึ้นและแก้ไขจุดเสียของ System V semaphore จนทำให้มีขนาดเล็กกระทัดรัด การรียกใช้งานง่าย และมีความทันสมัยรองรับสถาปัตยกรรมใหม่ๆได้ดีกว่ามาก
// 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 */voidhandler(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 */intmain() {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() */voidhandler(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...
// 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;intmain() {// initialize semaphore to 2sem_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);return0;}voiddoSomething(char c) {int i, time;for (i =0; i <3; i++) {// P operationif (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 operationsem_post(&sem); } }}void*doSomething1() {// thread AdoSomething('A');return0;}void*doSomething2() {// thread BdoSomething('B');return0;}void*doSomething3() {// thread CdoSomething('C');return0;}
$ 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
// sem3.c#include<semaphore.h>#include<pthread.h>#include<errno.h>#defineNUM_BOOTHS3#defineNUM_THREADS8typedefstruct {pthread_t id;int thread_number;} thread_data;/* * The voting booth semaphore. */sem_t semaphore;voiddo_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); }return0;}intmain(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);return0;}
$ 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.
// 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 */#defineMAXPIDS10voidworker(int i,void*args);intmain(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]);return1; } n =atoi(argv[1]);if (n > MAXPIDS)return1;#ifdefUSE_SHARED_MEMORYif ((id =shmget(IPC_PRIVATE,sizeof(sem_t), (S_IRUSR|S_IWUSR))) ==-1) {perror("Failed to create shared memory segment");return1;}if ((semlock = (sem_t*)shmat(id,NULL,0)) == (void*)-1) {perror("Failed to attach memory segment");return1;}#else /* try using ordinary process-private memory for sem_t variable */ semlock = (sem_t*) malloc(sizeof(sem_t));#endifif (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));return1; }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"); }#ifdefUSE_SHARED_MEMORYif (shmdt((void*)semlock)==-1) { /* shared memory detach */perror("Failed to destroy shared memory segment");return1;}#elsefree(semlock);#endifreturn0;}
// semexworker.c#include<errno.h>#include<semaphore.h>#include<stdio.h>#include<unistd.h>#defineTEN_MILLION10000000L#defineBUFSIZE1024voidworker(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;}
$ 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