
實驗六哲學家問題的線程版,大學生跟研究所學生的實驗内容是一樣的,但要求不一樣。大學生要求用信号量來實作,比較簡單,研究所學生用的是條件變量和互斥量來實作。
實驗描述
目的:學習線程的程式設計和同步。
要求:
1、程式文法
philosopher_th [ -t ]
N是哲學家的個數(N >= 2)。time是哲學家進餐和沉思的持續時間值,預設為2秒。
2、哲學家的編号為0 ~ N-1,分别用N個線程獨立模拟。
3、程式的輸出要簡潔,例如,當編号為3的哲學家在進餐時,就列印:
philosopher 3 is eating
而當他在沉思時,則列印:
philosopher 3 is thinking
不要輸出其他任何資訊。
4、使用pthread的semaphore.h提供的信号量同步線程。(大學生要求)
4、僅使用一個條件變量以及一個與其捆綁的鎖來同步線程。(研究所學生要求)
5、程式一直運作,直到人為地終止它(如按Ctrl-C或Ctrl-\)。不允許出現僵屍程序。
大學生實驗思路
用semaphore.h提供的信号量來解決的話,這個實驗就跟實驗四幾乎一樣了,就隻是同步機制不一樣而已,可以說,隻要替換下相應的同步函數(如用 sem_wait() 替代 lock() ),再稍做修改,這實驗就完成了…
是以并沒用什麼可以講的,注意下這個實驗是可以指定哲學家的數量,以及注意線程的最大限制數量就行了。《實驗指導》裡是這麼說的:注意,Linux系統對線程的處理與程序類似,是以,你能建立的線程數目與程序數目的總和,不能大于系統對你的限制,具體說就是20個。
完整代碼
編譯時需加上參數 -lpthread ,即:
gcc philosopher_th.c error2e.c -lpthread -o philosopher_th
參數 -lpthread 在使用 pthread 線程包的系統時是必須的。
#include "apue.h"
#include
#include
static sem_t *forks; // 信号量表示叉子狀态
static N; // 哲學家的個數
static int nsecs = 2;
static char now[80];
char* getTime() {
time_t tim;
struct tm *at;
time(&tim);
at=localtime(&tim);
strftime(now, 79, "%H:%M:%S", at);
return now;
}
void takeFork(int i) {
if(i == N-1) { // 人為設定第N-1位哲學家是右撇子
sem_wait(&forks[0]);
sem_wait(&forks[i]);
} else { // 其他是左撇子
sem_wait(&forks[i]);
sem_wait(&forks[i+1]);
}
}
void putFork(int i) {
if(i == N-1) {
sem_post(&forks[0]);
sem_post(&forks[i]);
}
else {
sem_post(&forks[i]);
sem_post(&forks[i+1]);
}
}
void thinking(int i, int nsecs) {
printf("philosopher %d is thinking\t%s\n", i, getTime());
sleep(nsecs);
}
void eating(int i, int nsecs) {
printf("philosopher %d is eating\t\t%s\n", i, getTime());
sleep(nsecs);
}
void* philosopher(void *n) {
int i = (int)n;
while(1) {
thinking(i, nsecs); // 哲學家i思考nsecs秒
takeFork(i); // 哲學家i拿起叉子
eating(i, nsecs); // 哲學家i進餐nsecs秒
putFork(i); // 哲學家i放下叉子
}
}
int main(int argc, char * argv[]) {
int i;
pthread_t tpid;
int status;
if ( (argc != 2 && argc != 4) || (argc == 4 && (strcmp(argv[2], "-t")) != 0)) {
err_quit("useage: philosopher_th [ -t ]");
}
N = atoi(argv[1]);
if (N < 2 || N > 20) {
err_quit("N is the number of philosopher, N >= 2 && N <= 20");
}
if (argc == 4) {
nsecs = atoi(argv[3]);
}
forks = (sem_t *) malloc( N * sizeof(sem_t) );
for( i = 0; i < N; i++) {
sem_init(forks + i, 0, 1);
}
for(i = 0; i < N; i++) {
status = pthread_create(&tpid, NULL, philosopher, (void *) i);
if( status < 0 ) {
err_sys("pthread_create fail.");
}
}
pthread_join(tpid, NULL);
}
研究所學生實驗思路
與實驗四類似,但使用的是線程同步,可以通過使用條件變量和一個互斥量來實作。同時以分離狀态來建立線程,讓線程終止時可以自動釋放資源。
哲學家的行為可以用如下函數描述:
void* philosopher(void * arg) {
int i = (int)arg;
while(1) {
thinking(i, nsecs);
pthread_mutex_lock(&pLock);
while(!isAvailable(i)) {
pthread_cond_wait(&pWait, &pLock);
}
takeFork(i);
pthread_mutex_unlock(&pLock);
eating(i, nsecs);
putFork(i);
pthread_cond_broadcast(&pWait); // 放下叉子後,喚醒等待的線程
}
}
先鎖住互斥量,然後嘗試獲得叉子,如果不能獲得叉子,則進入等待。此時pthread_cond_wait會自動釋放互斥量的鎖。而有哲學家拿起叉子,且進餐結束放下叉子後,通過pthread_cond_broadcast來喚醒等待的程序再次嘗試擷取叉子。
并且,使用條件變量+互斥量的話,可以簡單的以數組int *pFork來表示叉子(值為1表示叉子可用),并且不必考慮左、右撇子的問題,哲學家要麼拿起兩把叉子,要麼不拿叉子。在互斥鎖的保護下,這個操作不會有競争。
拿起叉子的代碼如下:
void takeFork(int i) {
pFork[i] = 0;
pFork[(i+1) % N] = 0;
}
完整代碼
編譯時需加上參數 -lpthread ,即:
gcc philosopher_th.c error2e.c -lpthread -o philosopher_th
參數 -lpthread 在使用 pthread 線程包的系統時是必須的。
#include "apue.h"
#include
static pthread_cond_t pWait = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t pLock = PTHREAD_MUTEX_INITIALIZER;
int N = 0;
int nsecs = 2;
int *pFork; // 01 array presents the forks; 1 -> available
int isAvailable(int i) {
if (pFork[i] && pFork[(i+1) % N])
return 1;
else
return 0;
}
void takeFork(int i) {
pFork[i] = 0;
pFork[(i+1) % N] = 0;
}
void putFork(int i) {
pFork[i] = 1;
pFork[(i+1) % N] = 1;
}
void thinking(int i, int nsecs) {
printf("philosopher %d is thinking\n", i);
sleep(nsecs);
}
void eating(int i, int nsecs) {
printf("philosopher %d is eating\n", i);
sleep(nsecs);
}
void* philosopher(void * arg) {
int i = (int)arg;
while(1) {
thinking(i, nsecs);
pthread_mutex_lock(&pLock);
while(!isAvailable(i)) {
// thinking(i, 0);
pthread_cond_wait(&pWait, &pLock);
}
takeFork(i);
pthread_mutex_unlock(&pLock);
eating(i, nsecs);
putFork(i);
pthread_cond_broadcast(&pWait);
}
}
int main(int argc, char *argv[]) {
int i, err;
pthread_t tid[20];
pthread_attr_t attr;
if ( (argc != 2 && argc != 4) || (argc == 4 && (strcmp(argv[2], "-t")) != 0)) {
err_quit("useage: philosopher_th [ -t ]");
}
N = atoi(argv[1]);
if (N < 2 || N > 20) {
err_quit("N is the number of philosopher, N >= 2 && N <= 20");
}
if (argc == 4) {
nsecs = atoi(argv[3]);
}
pFork = (int *)malloc(N*sizeof(int));
for (i = 0; i < N; i++) {
pFork[i] = 1;
}
err = pthread_attr_init(&attr); // detach
if (err != 0)
err_quit("pthread arr init error");
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (err != 0)
err_quit("set detachstate error");
for (i = 0; i < N; i++) {
err = pthread_create(&tid[i], &attr, philosopher, (void *)(long int)i);
if (err != 0)
err_quit("can't create thread: %s\n", strerror(err));
}
pause();
return 0;
}