实验六哲学家问题的线程版,本科生跟研究生的实验内容是一样的,但要求不一样。本科生要求用信号量来实现,比较简单,研究生用的是条件变量和互斥量来实现。
实验描述
目的:学习线程的编程和同步。
要求:
1、程序语法
philosopher_th
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 <pthread.h>
#include <semaphore.h>
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 <N> [ -t <time> ]");
}
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]);
}
/* 分配N个信号量 */
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 <pthread.h>
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 <N> [ -t <time> ]");
}
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;
}