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

ฟังก์ชันและตัวแปรที่เกี่ยวข้อง

  • ไลบรารีที่เกี่ยวข้องคือ semaphore.h

  • ตัวแปร semaphore descriptor ชื่อว่า sem_t

  • ฟังก์ชัน int sem_init(sem_t *sem, int shared, unsigned int value);

    • สำหรับสร้าง unnamed semaphore ที่ถูกชี้ไว้ด้วยตัวแปร sem ไปยัง value และสามารถกำหนดค่าใน shared ว่าตัว semaphore นี้จะสามารถถูกใช้ร่วมกันระหว่างโปรเซสได้หรือไม่ (ถ้าเป็นศูนย์ จะเป็นการระบุว่าจะไม่ถูกใช้ร่วมกัน)

    • สำหรับลบทำลายตัว unnamed semaphore ที่ถูกชี้ด้วยตัวแปร sem

  • ฟังก์ชัน sem_t sem_open(const char *name, int flags, ...);

    • สำหรับสร้าง named semaphore ตามชื่อของ name โดยมีให้เลือก flag ของตัวคือ O_CREAT สำหรับระบุว่าให้สร้าง semaphore ถ้ายังไม่มีอยู่ และ O_EXCL สำหรับระบุว่าให้ส่งผิดพลาดกลับมาด้วยถ้าพบว่ามี semaphore อยู่ก่อนหน้านี้แล้ว รวมทั้ง O_NONBLOCK สำหรับใช้ในโหมด non-blocking

  • ฟังก์ชัน int sem_close(sem_t *sem);

    • สำหรับปิดการเชื่อมต่อของพอยเตอร์ sem ของตัว named semaphore กับโปรเซสอื่นๆ (ยกเว้นโปรเซสที่เป็นคนเรียกฟังก์ชัน sem_open เท่านั้น)

  • ฟังก์ชัน int sem_unlink(const char name *name);

    • สำหรับลบ named semaphore ซึ่งจะถูกใช้หลังจากทุกโปรเซสได้สิ้นสุดและปิดการเชื่อมต่อกับ semaphore โดยการเรียกฟังก์ชัน sem_close

  • ฟังก์ชัน int sem_wait(sem_t *sem);

    • ถ้าค่า semaphore (sem) มีค่ามากกว่าศูนย์ ฟัก์ชันนี้จะทำการลบค่า semaphore ด้วย 1 แล้วคืนค่ากลับทันที ไม่เช่นนั้นโปรเซสก็จะถูกบล็อคไว้

  • ฟังก์ชัน int sem_trywait(sem_t *sem);

    • จะทำงานคล้ายกับฟังก์ชัน sem_wait() ยกเว้นถ้าค่า semaphore ไม่มากกว่าศูนย์ มันจะคืนค่ากลับเป็นความผิดพลาด

  • ฟังก์ชัน int sem_post(sem_t *sem);

    • สำหรับเพิ่มค่าของ semaphore (sem) ซึ่งถ้าค่าที่เพิ่มมีค่ามากกว่าศูนย์ โปรเซสก็อาจจะไม่ถูกบล็อค

  • ฟังก์ชัน int sem_getvalue(sem_t *sem, int *restrict sval);

    • สำหรับอ่านค่าของ semaphore (sem) และไปเก็บไว้ในตัวแปร sval

ตัวอย่างโครงร่างโปรแกรม (skeleton program) ของการเขียนโปรแกรม POSIX semaphore ดังแสดงข้างล่าง

#include <semaphore.h> 
int shared;
sem_t binary_sem; // Used like a mutex. 

voidthread_function(void ∗ arg) {
	sem_wait(&binary_sem); // Decrements count.
// Use shared resource.
	sem_post(&binary_sem); // Increments count.

}
void main(void) {
	sem_init(&binary_sem, 0, 1); // Initial count of 1.

// Start threads here.

	sem_wait(&binary_sem);
// Used shared resource.
	sem_post(&binary_sem);

// Join with threads here.
	sem_destroy(&binary_sem);
	return 0;
}

ตัวอย่างที่ 1

แสดงตัวอย่างการควบคุม 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...

ตัวอย่างที่ 2

แสดงตัวอย่างการสร้าง 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

ตัวอย่างที่ 3

แสดงตัวอย่างการจำลองสถานการณ์การลงคะแนนเลือกตั้ง (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.

ตัวอย่างที่ 4

แสดงตัวอย่างเมื่อแต่ละโปรเซสมีการใช้ 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)

ตัวอย่างที่ 5

แสดงตัวอย่างของเข้าถึงทรัพยากรกลางของโปรเซสที่เกี่ยวข้องกัน (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 ซึ่งทั้งโปรเซสแม่และโปรเซสลูกจะต้องเข้าใช้งานร่วมกัน

ตัวอย่างที่ 6

แสดงตัวอย่างการจัดจังหวะการเข้าใช้งานทรัพยากรระหว่างสองโปรเซสที่ไม่ได้มีความเกี่ยวข้องกัน (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);