实验六 线程及其同步—哲学家问题的线程版_Unix环境高级编程

大数据学习路线图

实验六哲学家问题的线程版,本科生跟研究生的实验内容是一样的,但要求不一样。本科生要求用信号量来实现,比较简单,研究生用的是条件变量和互斥量来实现。

实验描述

目的:学习线程的编程和同步。

要求:

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 <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;
}