C言語でセマフォを教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
本記事ではスレッドとプロセスを理解している前提で説明します.
スレッドとプロセスを学びたいあなたはこちらからどうぞ.
目次
セマフォ
セマフォとは,複数の実行単位(スレッドやプロセス)がクリティカルセククションに出入りする際,シンプルで便利な抽象化を提供する変数または抽象データ型です.
セマフォには以下の種類があります.
- カウンティングセマフォ:任意個の資源を扱うセマフォ
- バイナリセマフォ:値が0または1しか取らないセマフォ(ミューテックスと同等の機能)
セマフォとミューテックスの違いは,セマフォは任意個の状態をとることが可能なのに対して,ミューテックスはロックとアンロックの2つの状態しかとらないことです.
ミューテックスを知りたいあなたはこちらからどうぞ.
Linuxにおけるセマフォの実装としてSystem Vセマフォ(古いセマフォ)とPOSIXセマフォ(新しいセマフォ)があります.
System Vセマフォは多くのメモリを利用するため,あまり実用的ではありません.
そこで,本記事では,POSIXセマフォについて解説します.
POSIXセマフォ
POSIXセマフォが提供する関数を紹介します.
POSIXセマフォには名前なしセマフォと名前ありセマフォがあり,どちらかで利用できる関数と両方利用できる関数がありますので,それぞれ解説します.
sem_init関数(名前なしセマフォ)
1 |
int sem_init(sem_t *sem, int pshared, unsigned int value); |
sem_init関数は,名前なしセマフォを初期化する関数です.
第1引数semが指すアドレスにある名前なしセマフォを初期化します.
第2引数psharedは,このセマフォがプロセス内のスレッド間で共有されるのかプロセス間で共有されるのかを設定します.
psharedが0の場合,セマフォはプロセス内のスレッド間で共有されます.
psharedが0以外の場合,セマフォはプロセス間で共有されます.
第3引数valueは,そのセマフォの初期値を指定します.
sem_init関数は,成功すると0を返します.
エラーの場合は-1を返し,errnoにエラーを示す値を設定します.
sem_wait/sem_trywait関数(名前なしセマフォ,名前ありセマフォ)
1 2 |
int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); |
sem_wait/sem_trywait関数は,セマフォをロックする関数です.
sem_wait/sem_trywait関数は,名前なしセマフォと名前ありセマフォの両方で利用できます.
sem_wait関数は,第1引数semが指すセマフォの値を1減らします(ロックします).
セマフォの値が0より大きい場合,減算が実行され,関数は直ちに復帰します.
セマフォの現在値が0の場合には,減算を実行できるようになる(つまり,セマフォの値が0より大きな値になる)まで,もしくはシグナルハンドラにより呼び出しが中断されるまで,関数呼び出しは停止(block)します.
sem_trywait関数は,sem_wait関数と似ています.
sem_trywait関数とsem_wait関数の違いは,セマフォ値の減算をすぐに実行できなかった場合,停止(block)するのではなくエラーで復帰する(errnoにEAGAINがセットされる)ことです.
sem_wait/sem_trywait関数は,成功すると0を返します.
エラーの場合は-1を返し,errnoにエラーを示す値を設定します.
sem_post関数(名前なしセマフォ,名前ありセマフォ)
1 |
int sem_post(sem_t *sem); |
sem_post関数は,第1引数semが指すセマフォの値を1増やします(ロックを解除します).
これにより,セマフォの値は0より大きな値になります.
もしsem_wait関数で停止している別のスレッドやプロセスがある場合,そのスレッドやプロセスは起床し,セマフォをロックできるようになります.
sem_post関数は,成功すると0を返します.
エラーの場合は-1を返し,errnoにエラーを示す値を設定します.
sem_destroy関数(名前なしセマフォ)
1 |
int sem_destroy(sem_t *sem); |
sem_destroy関数は,第1引数semが指すアドレスにある名前なしセマフォを破棄します.
sem_destroy関数は,成功すると0を返します.
エラーの場合は-1を返し,errnoにエラーを示す値を設定します.
sem_open関数(名前ありセマフォ)
1 2 |
sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); |
sem_open関数は,新規のPOSIXセマフォを作成するか,既存のセマフォのオープンを行います.
セマフォは第1引数nameで識別されます.
名前ありセマフォは/somenameという形式の名前で識別されます.
その名前は,最大でNAME_MAX-4(すなわち 251)文字のヌル('\0')終端された文字列で,スラッシュで始まり,スラッシュ以外の文字が1文字以上続くフォーマットです(例:/semaphore).
第2引数oflagには,sem_open関数の動作を制御するフラグを指定します.
oflagにO_CREATが指定されると,まだ存在しない場合にはそのセマフォが作成されます.
oflagにO_CREATとO_EXCLの両方が指定された場合,指定された名前nameのセマフォが既に存在するとエラーが返されます.
oflagにO_CREATを指定する場合,さらに引数が2つ必要です.
※いわゆるC++言語のオーバーライド関数のような振る舞いで,おそらくweakシンボル「__attribute__((weak))」で実現しています.
第3引数modeは,open関数と同様に新しいセマフォに設定されるアクセス許可(permission)を指定します(例:0755,0644).
許可設定はプロセスのumaskでマスクされます.
セマフォにアクセスしようとするユーザは,読み出し許可と書き込み許可の両方を得る必要があります.
第4引数valueは新しいセマフォの初期値を指定します.
O_CREATが指定され,指定した名前nameのセマフォが既に存在する場合,modeとvalueは無視されます.
sem_open関数は,成功すると新しいセマフォのアドレスを返します.
このアドレスは他のセマフォ関連の関数を呼び出す際に利用されます.
エラーの場合,SEM_FAILED を返し,errnoにエラーを示す値を設定します.
sem_close関数(名前ありセマフォ)
1 |
int sem_close(sem_t *sem); |
sem_close関数は,第1引数semが参照する名前ありセマフォをクローズします.
このセマフォ用に呼び出し元プロセスにシステムが割り当てていたリソースを解放できるようにします.
sem_close関数は,成功すると0を返します.
エラーの場合は-1を返し,errnoにエラーを示す値を設定します.
sem_unlink関数(名前ありセマフォ)
1 |
int sem_unlink(const char *name); |
sem_unlink関数は,第1引数nameで参照される名前ありセマフォを削除します.
名前ありセマフォの名前は直ちに削除されます.
このセマフォをオープンしている他のすべてのプロセスがセマフォを閉じた後にセマフォの削除が行われます.
sem_unlink関数は,成功すると0を返します.
エラーの場合は-1を返し,errnoにエラーを示す値を設定します.
名前なしセマフォの利用例
名前なしセマフォの利用例を紹介します.
名前なしセマフォでスレッド間のロックにsem_wait関数を利用
名前なしセマフォでスレッド間のロックにsem_wait関数を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define LOOP_NUM 50000 #define NR_THREADS 2 void *func(void *arg) { size_t i; #ifndef NO_LOCK sem_t *sem = (sem_t *)((void **) arg)[0]; #endif int *count = (int *)((void **) arg)[1]; for (i = 0; i < LOOP_NUM; i++) { #ifndef NO_LOCK if (sem_wait(sem) != 0) { fprintf(stderr, "Error: cannot lock\n"); exit(1); } /* critical section with lock. */ (*count)++; if (sem_post(sem) != 0) { fprintf(stderr, "Error: cannot unlock\n"); exit(2); } #else /* critical section without lock (interleaving increment operations). */ (*count)++; #endif } return NULL; } int main(void) { pthread_t threads[NR_THREADS]; size_t i; sem_t sem; int count = 0; void *arg[] = {&sem, &count}; if (sem_init(&sem, 0, 1) == -1) { perror("sem_init"); exit(1); } for (i = 0; i < NR_THREADS; i++) { if (pthread_create(&threads[i], NULL, func, arg) != 0) { fprintf(stderr, "Error: cannot create thread %zu\n", i + 1); exit(2); } } for (i = 0; i < NR_THREADS; i++) { printf("execute pthread_join thread %zu\n", i + 1); if (pthread_join(threads[i], NULL) != 0) { fprintf(stderr, "Error: cannot join thread %zu\n", i + 1); exit(3); } } printf("count = %d\n", count); if (sem_destroy(&sem) == -1) { perror("sem_destroy"); exit(4); } return 0; } |
実行結果は以下になります.
countの数値がLOOP_NUM(=50000)の2倍,つまり50000 * 2 = 100000になることがわかります.
1 2 3 4 5 |
$ gcc semaphore_between_threads.c -lpthread $ a.out execute pthread_join thread 1 execute pthread_join thread 2 count = 100000 |
-DNO_LOCKオプションを付けてコンパイルした場合の実行結果は以下になります.
5行目の「count = 70019」で100000より小さい値になっていることがわかります.(100000や他の値になる場合もあります.)
この理由は,Linuxはプリエンプティブ・マルチタスクの実行が可能なため,countのインクリメント処理でインターリーブが発生してしまうからです.
1 2 3 4 5 |
$ gcc semaphore_between_threads.c -lpthread -DNO_LOCK $ a.out execute pthread_join thread 1 execute pthread_join thread 2 count = 70019 |
名前なしセマフォでスレッド間のロックにsem_trywait関数を利用
名前なしセマフォでスレッド間のロックにsem_trywait関数を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <pthread.h> #include <semaphore.h> #define LOOP_NUM 50000 #define NR_THREADS 2 void *func(void *arg) { size_t i; #ifndef NO_LOCK sem_t *sem = (sem_t *)((void **) arg)[0]; #endif int *count = (int *)((void **) arg)[1]; for (i = 0; i < LOOP_NUM; i++) { #ifndef NO_LOCK while (true) { if (sem_trywait(sem) == 0) { /* critical section with lock. */ (*count)++; if (sem_post(sem) != 0) { fprintf(stderr, "Error: cannot unlock\n"); exit(1); } break; } /* cannot lock and do next trial. */ } #else /* critical section without lock (interleaving increment operations). */ (*count)++; #endif } return NULL; } int main(void) { pthread_t threads[NR_THREADS]; size_t i; sem_t sem; int count = 0; void *arg[] = {&sem, &count}; if (sem_init(&sem, 0, 1) == -1) { perror("sem_init"); exit(1); } for (i = 0; i < NR_THREADS; i++) { if (pthread_create(&threads[i], NULL, func, arg) != 0) { fprintf(stderr, "Error: cannot create thread %zu\n", i + 1); exit(2); } } for (i = 0; i < NR_THREADS; i++) { printf("execute pthread_join thread %zu\n", i + 1); if (pthread_join(threads[i], NULL) != 0) { fprintf(stderr, "Error: cannot join thread %zu\n", i + 1); exit(3); } } printf("count = %d\n", count); if (sem_destroy(&sem) == -1) { perror("sem_destroy"); exit(4); } return 0; } |
実行結果は以下になります.同様です.
1 2 3 4 5 |
$ gcc semaphore_between_threads2.c -lpthread $ a.out execute pthread_join thread 1 execute pthread_join thread 2 count = 100000 |
-DNO_LOCKオプションを付けてコンパイルした場合の実行結果は以下になります.同様です.
1 2 3 4 5 |
$ gcc semaphore_between_threads2.c -lpthread -DNO_LOCK $ a.out execute pthread_join thread 1 execute pthread_join thread 2 count = 86208 |
名前なしセマフォでプロセス間のロックにsem_wait関数を利用
名前なしセマフォでプロセス間のロックにsem_wait関数を利用するコードは以下になります.
shm_open/shm_unlink関数でプロセス間の共有メモリの作成と削除を行っています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> #include <sys/wait.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #define LOOP_NUM 50000 #define SHM_NAME "/shared_memory" #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) void func(sem_t *sem, int *count) { size_t i; for (i = 0; i < LOOP_NUM; i++) { #ifndef NO_LOCK if (sem_wait(sem) != 0) { fprintf(stderr, "Error: cannot lock\n"); exit(1); } /* critical section with lock. */ (*count)++; if (sem_post(sem) != 0) { fprintf(stderr, "Error: cannot unlock\n"); exit(2); } #else /* critical section without lock (interleaving increment operations). */ (*count)++; #endif } } int main(void) { sem_t *sem; pid_t pid; int *count; int fd; size_t size = sizeof(sem_t) + sizeof(int); if ((fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, FILE_MODE)) == -1) { perror("shm_open"); exit(1); } if (ftruncate(fd, size) == -1) { perror("ftruncate"); exit(2); } if ((sem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { perror("mmap"); exit(3); } count = (int *)(sem + sizeof(sem_t)); *count = 0; if (sem_init(sem, 1, 1) == -1) { perror("sem_init"); exit(4); } if ((pid = fork()) > 0) { printf("execute parent\n"); } else if (pid == 0) { printf("execute child\n"); } else { perror("fork"); exit(5); } func(sem, count); printf("*count = %d\n", *count); if (munmap(sem, size) == -1) { perror("munmap"); exit(6); } if (pid != 0) { wait(NULL); if (shm_unlink(SHM_NAME) != 0) { perror("shm_unlink"); exit(7); } } if (sem_destroy(sem) == -1) { perror("sem_destroy"); exit(8); } return 0; } |
実行結果は以下になります.
shm_open/shm_unlink関数を利用する際は-lrtオプションが必要なことに注意して下さい.
6行目の「*count = 100000」で正常にカウントできていることがわかります.
※5行目の*countは先に実行終了した親プロセスまたは子プロセスの表示です.
1 2 3 4 5 6 |
$ gcc semaphore_between_processes.c -lpthread -lrt $ a.out execute parent execute child *count = 95088 *count = 100000 |
-DNO_LOCKオプションを付けてコンパイルした場合の実行結果は以下になります.
6行目の「*count = 87387」で100000より小さい値になっていることがわかります.
1 2 3 4 5 6 |
$ gcc semaphore_between_processes.c -lpthread -lrt -DNO_LOCK $ a.out execute parent execute child *count = 44993 *count = 87387 |
名前なしセマフォでプロセス間のロックにsem_trywait関数を利用
名前なしセマフォでプロセス間のロックにsem_trywait関数を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <unistd.h> #include <semaphore.h> #include <sys/wait.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #define LOOP_NUM 50000 #define SHM_NAME "/shared_memory" #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) void func(sem_t *sem, int *count) { size_t i; for (i = 0; i < LOOP_NUM; i++) { #ifndef NO_LOCK while (true) { if (sem_trywait(sem) == 0) { /* critical section with lock. */ (*count)++; if (sem_post(sem) != 0) { fprintf(stderr, "Error: cannot unlock\n"); exit(1); } break; } /* cannot lock and do next trial. */ } #else /* critical section without lock (interleaving increment operations). */ (*count)++; #endif } } int main(void) { sem_t *sem; pid_t pid; int *count; int fd; size_t size = sizeof(sem_t) + sizeof(int); if ((fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, FILE_MODE)) == -1) { perror("shm_open"); exit(1); } if (ftruncate(fd, size) == -1) { perror("ftruncate"); exit(2); } if ((sem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { perror("mmap"); exit(3); } count = (int *)(sem + sizeof(sem_t)); *count = 0; if (sem_init(sem, 1, 1) == -1) { perror("sem_init"); exit(4); } if ((pid = fork()) > 0) { printf("execute parent\n"); } else if (pid == 0) { printf("execute child\n"); } else { perror("fork"); exit(5); } func(sem, count); printf("*count = %d\n", *count); if (munmap(sem, size) == -1) { perror("munmap"); exit(6); } if (pid != 0) { wait(NULL); if (shm_unlink(SHM_NAME) != 0) { perror("shm_unlink"); exit(7); } } if (sem_destroy(sem) == -1) { perror("sem_destroy"); exit(8); } return 0; } |
実行結果は以下になります.同様です.
1 2 3 4 5 6 |
$ gcc semaphore_between_processes2.c -lpthread -lrt $ a.out execute parent execute child *count = 93629 *count = 100000 |
-DNO_LOCKオプションを付けてコンパイルした場合の実行結果は以下になります.同様です.
1 2 3 4 5 6 |
$ gcc semaphore_between_processes2.c -lpthread -lrt -DNO_LOCK $ a.out execute parent execute child *count = 50000 *count = 79590 |
名前ありセマフォの利用例
名前ありセマフォの利用例を紹介します.
名前ありセマフォでスレッド間のロックにsem_wait関数を利用
名前ありセマフォでスレッド間のロックにsem_wait関数を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> #define LOOP_NUM 50000 #define NR_THREADS 2 #define SEM_NAME "/semaphore" void *func(void *arg) { size_t i; #ifndef NO_LOCK sem_t *sem = *(sem_t **)((void **) arg)[0]; #endif int *count = (int *)((void **) arg)[1]; for (i = 0; i < LOOP_NUM; i++) { #ifndef NO_LOCK if (sem_wait(sem) != 0) { fprintf(stderr, "Error: cannot lock\n"); exit(1); } /* critical section with lock. */ (*count)++; if (sem_post(sem) != 0) { fprintf(stderr, "Error: cannot unlock\n"); exit(2); } #else /* critical section without lock (interleaving increment operations). */ (*count)++; #endif } return NULL; } int main(void) { pthread_t threads[NR_THREADS]; size_t i; sem_t *sem; int count = 0; void *arg[] = {&sem, &count}; if ((sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0644, 1)) == SEM_FAILED) { perror("sem_open"); exit(1); } for (i = 0; i < NR_THREADS; i++) { if (pthread_create(&threads[i], NULL, func, arg) != 0) { fprintf(stderr, "Error: cannot create thread %zu\n", i + 1); exit(2); } } for (i = 0; i < NR_THREADS; i++) { printf("execute pthread_join thread %zu\n", i + 1); if (pthread_join(threads[i], NULL) != 0) { fprintf(stderr, "Error: cannot join thread %zu\n", i + 1); exit(3); } } printf("count = %d\n", count); if (sem_close(sem) == -1) { perror("sem_close"); exit(4); } if (sem_unlink(SEM_NAME) == -1) { perror("sem_unlink"); exit(5); } return 0; } |
実行結果は以下になります.同様です.
1 2 3 4 5 |
$ gcc semaphore2_between_threads.c -lpthread $ a.out execute pthread_join thread 1 execute pthread_join thread 2 count = 100000 |
-DNO_LOCKオプションを付けてコンパイルした場合の実行結果は以下になります.同様です.
1 2 3 4 5 |
$ gcc semaphore2_between_threads.c -lpthread -DNO_LOCK $ a.out execute pthread_join thread 1 execute pthread_join thread 2 count = 71543 |
名前ありセマフォでスレッド間のロックにsem_trywait関数を利用
名前ありセマフォでスレッド間のロックにsem_trywait関数を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <pthread.h> #include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> #define LOOP_NUM 50000 #define NR_THREADS 2 #define SEM_NAME "/semaphore" void *func(void *arg) { size_t i; #ifndef NO_LOCK sem_t *sem = *(sem_t **)(((void **) arg)[0]); #endif int *count = (int *)((void **) arg)[1]; for (i = 0; i < LOOP_NUM; i++) { #ifndef NO_LOCK while (true) { if (sem_trywait(sem) == 0) { /* critical section with lock. */ (*count)++; if (sem_post(sem) != 0) { fprintf(stderr, "Error: cannot unlock\n"); exit(1); } break; } /* cannot lock and do next trial. */ } #else /* critical section without lock (interleaving increment operations). */ (*count)++; #endif } return NULL; } int main(void) { pthread_t threads[NR_THREADS]; size_t i; sem_t *sem; int count = 0; void *arg[] = {&sem, &count}; if ((sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0644, 1)) == SEM_FAILED) { perror("sem_open"); exit(1); } for (i = 0; i < NR_THREADS; i++) { if (pthread_create(&threads[i], NULL, func, arg) != 0) { fprintf(stderr, "Error: cannot create thread %zu\n", i + 1); exit(2); } } for (i = 0; i < NR_THREADS; i++) { printf("execute pthread_join thread %zu\n", i + 1); if (pthread_join(threads[i], NULL) != 0) { fprintf(stderr, "Error: cannot join thread %zu\n", i + 1); exit(3); } } printf("count = %d\n", count); if (sem_close(sem) == -1) { perror("sem_close"); exit(4); } if (sem_unlink(SEM_NAME) == -1) { perror("sem_unlink"); exit(5); } return 0; } |
実行結果は以下になります.同様です.
1 2 3 4 5 |
$ gcc semaphore2_between_threads2.c -lpthread $ a.out execute pthread_join thread 1 execute pthread_join thread 2 count = 100000 |
-DNO_LOCKオプションを付けてコンパイルした場合の実行結果は以下になります.同様です.
1 2 3 4 5 |
$ gcc semaphore2_between_threads2.c -lpthread -DNO_LOCK $ a.out execute pthread_join thread 1 execute pthread_join thread 2 count = 67632 |
名前ありセマフォでプロセス間のロックにsem_wait関数を利用
名前ありセマフォでプロセス間のロックにsem_wait関数を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> #include <sys/wait.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/stat.h> #define LOOP_NUM 50000 #define SHM_NAME "/shared_memory" #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) #define SEM_NAME "/semaphore" void func(sem_t *sem, int *count) { size_t i; for (i = 0; i < LOOP_NUM; i++) { #ifndef NO_LOCK if (sem_wait(sem) != 0) { fprintf(stderr, "Error: cannot lock\n"); exit(1); } /* critical section with lock. */ (*count)++; if (sem_post(sem) != 0) { fprintf(stderr, "Error: cannot unlock\n"); exit(2); } #else /* critical section without lock (interleaving increment operations). */ (*count)++; #endif } } int main(void) { sem_t *sem; pid_t pid; int *count; int fd; size_t size = sizeof(sem_t) + sizeof(int); if ((fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, FILE_MODE)) == -1) { perror("shm_open"); exit(1); } if (ftruncate(fd, size) == -1) { perror("ftruncate"); exit(2); } if ((sem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { perror("mmap"); exit(3); } count = (int *)(sem + sizeof(sem_t)); *count = 0; if ((sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0644, 1)) == SEM_FAILED) { perror("sem_open"); exit(4); } if ((pid = fork()) > 0) { printf("execute parent\n"); } else if (pid == 0) { printf("execute child\n"); } else { perror("fork"); exit(5); } func(sem, count); printf("*count = %d\n", *count); if (munmap(sem, size) == -1) { perror("munmap"); exit(6); } if (pid != 0) { wait(NULL); if (shm_unlink(SHM_NAME) != 0) { perror("shm_unlink"); exit(7); } if (sem_close(sem) == -1) { perror("sem_close"); exit(8); } if (sem_unlink(SEM_NAME) == -1) { perror("sem_unlink"); exit(9); } } return 0; } |
実行結果は以下になります.同様です.
1 2 3 4 5 6 |
$ gcc semaphore2_between_processes.c -lpthread -lrt $ a.out execute parent execute child *count = 75851 *count = 100000 |
-DNO_LOCKオプションを付けてコンパイルした場合の実行結果は以下になります.同様です.
1 2 3 4 5 6 |
$ gcc semaphore2_between_processes.c -lpthread -lrt -DNO_LOCK $ a.out execute parent execute child *count = 50000 *count = 92124 |
名前ありセマフォでプロセス間のロックにsem_trywait関数を利用
名前ありセマフォでプロセス間のロックにsem_trywait関数を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <unistd.h> #include <semaphore.h> #include <sys/wait.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/stat.h> #define LOOP_NUM 50000 #define SHM_NAME "/shared_memory" #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) #define SEM_NAME "/semaphore" void func(sem_t *sem, int *count) { size_t i; for (i = 0; i < LOOP_NUM; i++) { #ifndef NO_LOCK while (true) { if (sem_trywait(sem) == 0) { /* critical section with lock. */ (*count)++; if (sem_post(sem) != 0) { fprintf(stderr, "Error: cannot unlock\n"); exit(1); } break; } /* cannot lock and do next trial. */ } #else /* critical section without lock (interleaving increment operations). */ (*count)++; #endif } } int main(void) { sem_t *sem; pid_t pid; int *count; int fd; size_t size = sizeof(sem_t) + sizeof(int); if ((fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, FILE_MODE)) == -1) { perror("shm_open"); exit(1); } if (ftruncate(fd, size) == -1) { perror("ftruncate"); exit(2); } if ((sem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { perror("mmap"); exit(3); } count = (int *)(sem + sizeof(sem_t)); *count = 0; if ((sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0644, 1)) == SEM_FAILED) { perror("sem_open"); exit(4); } if ((pid = fork()) > 0) { printf("execute parent\n"); } else if (pid == 0) { printf("execute child\n"); } else { perror("fork"); exit(5); } func(sem, count); printf("*count = %d\n", *count); if (munmap(sem, size) == -1) { perror("munmap"); exit(6); } if (pid != 0) { wait(NULL); if (shm_unlink(SHM_NAME) != 0) { perror("shm_unlink"); exit(7); } if (sem_close(sem) == -1) { perror("sem_close"); exit(8); } if (sem_unlink(SEM_NAME) == -1) { perror("sem_unlink"); exit(9); } } return 0; } |
実行結果は以下になります.同様です.
1 2 3 4 5 6 |
$ gcc semaphore2_between_processes2.c -lpthread -lrt $ a.out execute parent execute child *count = 95591 *count = 100000 |
-DNO_LOCKオプションを付けてコンパイルした場合の実行結果は以下になります.同様です.
1 2 3 4 5 6 |
$ gcc semaphore2_between_processes2.c -lpthread -lrt -DNO_LOCK $ a.out execute parent execute child *count = 50000 *count = 75935 |
まとめ
C言語でセマフォを紹介しました.
具体的には,POSIXセマフォの名前ありセマフォと名前なしセマフォで,スレッド間とプロセス間のロックにsem_wait/sem_trywait関数を利用するコードを解説しました.
結構難しいコードですが,何度も読んで理解を深めましょう!
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!