[Lv4]Philosopher
https://github.com/syamashi/philosopher
・[mandatory] pthread / mutexを使いこなす
新しいコマンドを知る。
sand.c
#include <stdio.h>
#include <pthread.h>
/*
** mainで宣言したcntを、10000カウントアップするだけの関数。
*/
void *f(void *p)
{
int *cnt;
cnt = p;
for (int i = 0; i < 10000; ++i)
++(*cnt);
return (NULL);
}
int main()
{
int cnt = 0;
pthread_t thread1;
pthread_t thread2;
// mainの処理とは別に働かせる。並列処理。
pthread_create(&thread1, NULL, &f, &cnt);
pthread_create(&thread2, NULL, &f, &cnt);
// 終了するまで待機。pthread_createとセット
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("cnt: %d\n", cnt); // 20000がでると思った?
}
・pthread_t、並列処理の数だけ作る。
・pthread_create()、並列処理開始。
第3引数に渡す関数が仕事。これを作るのがメインタスク。
第4引数が引き継げる値。4byteのデータだけしか引き継げない。から、&でポインタ渡せばいいね。
create()したら、join()かdetach()して終了処理が必要。
・pthread_join()、create()が終了するまでmainが待機する。
・pthraed_detach()。join()のバリエーション。mainは待機せず先に進む。
Q. pthread~~が見つからなくてコンパイルできない?
A. -pthreadをつけてコンパイル。
gcc -pthread *.c && ./a.out
出力例
./a.out
cnt: 20000
./a.out
cnt: 13022
./a.out
cnt: 19152
pthread_1とpthread_2でカウントアップのタイミングが重なってしまうと、
1. pthread_1が、*cnt=10 を見る。
2. pthread_2が、*cnt=10を見てしまう。
3. pthread_1が、*cnt=11にカウントアップする。
4. pthread_2が、*cnt=11に上書きする。
20000回加算してるのに、20000UPしない事件が発生。
カウントアップが重ならないように、信号を設置します。追記。
sand.c
#include <stdio.h>
#include <pthread.h>
#include <stdbool.h>
typedef struct s_mut
{
pthread_mutex_t *mutex;
int *cnt;
} t_mut;
void *f(void *p)
{
t_mut *t;
t = p;
for (int i = 0; i < 10000; ++i)
{
// lock ~ unlock の中の処理を動かせるのは1人だけ。lock()で待機する。
pthread_mutex_lock(t->mutex);
++*t->cnt;
pthread_mutex_unlock(t->mutex);
}
return (NULL);
}
int main()
{
int cnt = 0;
pthread_t thread1;
pthread_t thread2;
// 歩行者信号の設置 initで、最初unlock状態がセットされる。単純なboolではなさそう。
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
// pthreadに、 cnt と mutex を渡したいので構造体にいれる。
t_mut t;
t.mutex = &mutex;
t.cnt = &cnt;
// 関数を勝手に走らせる。並列処理。
pthread_create(&thread1, NULL, &f, &t);
pthread_create(&thread2, NULL, &f, &t);
// 終了するまで待機。createとセット
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("*t.cnt: %d\n", *t.cnt);
}
・pthread_mutex_tが歩行者信号。フラグの数だけ作る。ON/OFFしか管理しない。
・pthread_mutex_init(&mutex, NULL);、mutexの初期値いれ。
・pthread_mutex_lock()、ロック処理。もしpthread1がロックした場合、pthread2はロックで待機する。
・pthread_mutex_unlock()、アンロック処理。もしpthread1がアンロックしたら、ロック待ちしてたpthread2が動きだす。pthread2がロックする。
Q. pthread_mutex_tって、もはやbool?
A. 俺もそう思う。値の確認はできなそう
出力例
./a.out
*t.cnt: 20000
./a.out
*t.cnt: 20000
./a.out
*t.cnt: 20000
これで、並列処理と、待機処理が実装できました。
Q. 無限ロック?
A. たとえばlockを2回連続で書きます。
pthread_mutex_lock()
pthread_mutex_lock()
これをすると、unlock()に進む道が途絶え、完全に処理が進まなくなり、ゲーム終了。
pthread_create()の処理の中でたくさんlock()すると思いますが、絶対に、全部unlock()してからタスクを終わらせるようにします。
・[bonus] fork / semaphore を使いこなす
semaphoreの新しいコマンドを知る。5回くらい読んだ。
sem_overview - 約束事その他の説明 - Linux コマンド集 一覧表
sand.c
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <unistd.h>
# include <pthread.h>
# include <stdbool.h>
# include <semaphore.h>
# include <sys/time.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/wait.h>
# include <fcntl.h>
# include <signal.h>
void *func(void *p)
{
sem_t *sem2 = p;
for (int i = 0; i < 5; ++i){
sem_wait(sem2);
printf("[func]%d called\n", i);
}
return (NULL);
}
int main()
{
sem_t *sem;
sem_t *sem2;
pid_t pid;
int status;
pthread_t thread;
sem_unlink("/aaa");
sem = sem_open("/aaa", O_CREAT, 0600, 2); // "/" から始まれば名前付きsem。同じ名前で共有できる。
sem2 = sem; // アドレス共有するだけで実態は同じ
sem_unlink("/aaa");// 全プロセスでこれがクローズされたら、自動でセマフォ削除される
pid = fork();
if (pid == 0)
{
pthread_create(&thread, NULL, &func, sem2);
pthread_join(thread, NULL);
exit(0);
}
int microsecond = 1.5 * 1000000;
for (int i = 0; i < 3; ++i){
usleep(microsecond);
sem_post(sem);
}
waitpid(-1, &status, 0);
sem_close(sem);
return (0);
}
関数ごとのイメージ。
・sem_open()で信号の設置。同時処理の許容数がいれられる。
- 第4引数が許容数。
- 第1引数の先頭 "/" で名前付きセマフォ。
- /dev/shm(Linux環境)に sem.xxx が作られる。これが記憶領域なのだろう。
・sem_waitでタスク発動。許容数が1減る。
・sem_postでタスク補充。許容数が1増える。
・sem_t*の中にタスク回数入ってる。参照はできなそう。
・sem_unlink()でclose()されているセマフォを削除できる。
・sem_close()でセマフォの停止?削除はしていなそう。
出力例
// sem_open(2) で初期値2なので、とりあえず2回すぐに出力
[func]0 called
[func]1 called
// usleep + sem_post()毎に動くため、1.5秒おきに出力
[func]2 called
[func]3 called
[func]4 called
Q. sem_open()の許容数を変更したのに適応されないんだけど?
A. 2回目のコンパイルでしょうか。
すでに指定名のセマフォが作られている場合、許容数の変更は適応されません。
- [Linux] /dev/shm に作成される。
- [Mac] lsof -c <command> | grep PSXSEM で実行中コマンドのセマフォが分かる
- 参考
- https://stackoverflow.com/questions/11393411/how-to-list-posix-semaphores-on-mac-os-x
sem_open()の前後の行でsem_unlink()して、初期化 + 削除を指示すると、毎回新しいsemを作成できます。(それがベストプラクティスかは知らない)
Q. sem_open()の直後にsem_unlink()?
A. すぐに消えるわけじゃないです。sem_close()した後に削除する、という指示のようです。
超絶有能visualizer。もし結果出力したらここに貼るだけ。
https://nafuka11.github.io/philosophers-visualizer/
この記事が気に入ったらサポートをしてみませんか?