一. 实验目的
- 理解进程/线程的概念和应用编程过程
- 理解进程/线程的同步机制和应用编程
二. 实验内容
- 在Linux下创建一队父子进程
- 在Linux下创建两个线程A和B,循环输出数据或字符串
- 在Linux下创建父子进程,实验wait同步函数,理解父子进程同步
- 在Linux下利用线程实现“生产者-消费者”同步控制
- 在Linux 下模拟哲学家 就餐,提供死锁和非死锁解法
三. 实验过程
3.1 在Linux 下创建一对父子进程
- 分别输出各自的进程号,父进程号和特别的提示字符串信息
- 让父进程提前结束或后结束,观察子进程的父进程ID
- 使用PS命令查看进程列表信息,核对进程号,父进程号
要创建子进程,需要用到 fork() 函数,该函数的函数原型声明在 unistd.h 中
pid_t fork(void);
返回值:
如果成功,父进程中,将返回子进程的pid(pid>0);子进程中,将返回0 (pid =0)。
如果失败,父进程中,会返回-1,而子进程不会被创建。并且设置errno的值来指出这个错误。
由 fork 创建的新进程被称为子进程。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。子进程可以调用getpid()来获取自己的pid;也可以调用getppid()来获取父进程的id。
fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置
创建一个c文件 使用命令vim fork.c
,输入以下代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
void main()
{
pid_t pid;
fprintf(stdout, "I am the first process, my pid is %d, my parent pid is %d\n", getpid(), getppid());
pid = fork();
if (0 == pid)
fprintf(stdout, "I am the child procress, my pid value is %d, but my real pid is %d, my parent pid is %d\n", pid, getpid(), getppid());
else
fprintf(stdout, "I am the parent process, my child pid is %d, my pid is %d, my parent pid is %d\n", pid, getpid(), getppid());
// 让该线程睡眠一会,从而使用ps 命令查看线程时,该线程没有结束
// 睡眠50s
sleep(50);
}
编译gcc -o fork.out fork.c
输出结果
我们运行的 ./fork.out 本身也是一个进程,它的pid是5833,ppid是2765(它的parent是bash)
使用fork()函数后创建的子进程的pid是5834,ppid是5833
使用命令ps -ef
第二列是pid,第三列是ppid(父进程的pid)。可以发现进程号和父进程号是符合的。
为了观察 让父进程提前结束或后结束,观察子进程的父进程ID
让父进程提前结束
修改 fork.c文件(也可以再创建一个)
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("start program %s, pid: %d, ppid: %d \n", argv[0], getpid(), getppid());
pid_t pid = fork();
if (-1 == pid) {
printf("fork process failed. errno: %u, error: %s\n", errno, strerror(errno));
exit(-1);
}
if (pid > 0) { // parent
printf("parent process\n");
sleep(1);
/** 标记一、wait 等待子进程结束*/
// int status;
// wait(&status);
} else { // child
printf("child process, pid: %d, ppid: %d\n", getpid(), getppid());
for (int i = 0; i < 5; i++) {
printf("child sleep %ds\n", i);
sleep(1);
}
printf("##child process, pid: %d, ppid: %d\n", getpid(), getppid());
}
return 0;
}
再次编译 gcc -o fork.out fork.c
运行./fork.out
输出结果
发现父线程先关闭后,子线程的ppid由6040 变为了 1892
1892对应命令 /lib/systemd/systemd/ --user 代表systemd进程,这里有人可能会疑惑为什么不是init进程(ppid不是1)。
其实systemd 是 init 的现代替代品。绝大多数linux发行版(ubuntu, fedora, centos, arch 等等)已经用systemd替换了传统的init。systemd 担任的角色非常多,其中之一负责管理用户会话,也就是作为用户图形界面的父进程进行管理。systemd --user 会调用 prctl,接替全局的init进程的职责,处理所有孤儿进程。
如果在tty界面下实验,可以按ctrl + alt + F3
打开tty界面,再次运行该程序,发现父进程先结束后,子进程的ppid变为了符合预期的1.也就是被init进程接管了。 之后可以使用ctrl + alt +F2
返回图形界面。
让父进程后结束
把“标志一”的wait代码删除注释,使用以下代码
/** 标记一、wait 等待子进程结束 */
int status;
wait(&status);
同样步骤编译运行后结果显示为
wait函数的作用是等待子进程结束,并获取子进程结束的状态,所以父进程wait等待子进程结束后才继续运行退出,故子进程的ppid一直是原来fork它的父进程的pid,一直是6232.
3.2 在Linux下创建两个线程A和B,循环输出数据或字符串
- 使用pthread线程库
- 线程A递增输出1-1000; 线程B递减输出1000-1。为避免输出太快,每隔0.2s(可自行调节)输出一个数。
- 输出数据时,同时输出A或B以标识是哪个线程输出的,并注意格式化输出信息。
线程相关函数 pthread_xxx 都在头文件 pthread.h里。
函数 pthread_create():用于创建线程
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg);
- thread: 返回的线程句柄
- attr : 指定线程属性
- start_routine : 线程函数入口地址
- arg : 线程函数参数
函数 pthread_join: 用于等待目标线程终止
int pthread_join(pthread_t thread, void **retval);
- thread :目标线程
- retval :NULL即可
创建一个c文件(如trial2.c),并输入以下代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
// sleep xx毫秒
int msleep(long msec)
{
struct timespec ts;
int res;
if (msec < 0)
{
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
return res;
}
//打印 1~1000
void * print1()
{
int i = 0;
while (i <= 1000)
{
printf("A:%d\n", i);
i++;
// 间隔0.2秒
msleep(200);
}
}
//打印1000~1
void * print2()
{
int i = 1000;
while (i > 0)
{
printf("B:%d\n", i);
i--;
// 间隔0.2秒
msleep(200);
}
}
int main()
{
pthread_t thread1;
pthread_t thread2;
int hThread1 = pthread_create(&thread1, NULL, print1, NULL);
int hThread2 = pthread_create(&thread2, NULL, print2, NULL);
if (hThread1 != 0)
{
printf("hThread1 err");
}
else if (hThread2 != 0)
{
printf("hThread2 err!");
}
pthread_join(thread1, NULL); //
pthread_join(thread2, NULL); //
return 0;
}
msleep() 函数参考Is there an alternative sleep function in C to milliseconds?
输入指令gcc -o trial2.out trial2.c -pthread
进行编译,再输入./trial2.out
运行
其中注意是 -pthread 而不是 -plthread,解释可见Undefined reference to pthread_create in Linux和Compiling Threaded Programs
可参考以下表格
3.3 在Linux下创建父子进程,实验wait同步函数,理解父子进程同步
- 子进程休眠5秒,父进程不休眠。子进程用exit返回参数
- 父进程调用wait等待子进程先结束,并分析子进程返回参数
- 父进程输出子进程的返回信息
wait函数 :用于等待一个子进程停止或终止(随机的一个子进程)
#include <sys/wait.h>
pid_t wait(int *stat_loc);
- stat_loc: 存储子进程结束时的返回值
创建新文件(如trial3.c),并且输入以下代码
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("start program %s, pid: %d, ppid: %d \n", argv[0], getpid(), getppid());
pid_t pid = fork();
if (-1 == pid) {
printf("fork process failed. errno: %u, error: %s\n", errno, strerror(errno));
exit(-1);
}
if (pid > 0) { // parent
int status;
wait(&status);
printf("child process return %d\n",status);
} else { // child
// 子进程休眠5s
sleep(5);
// 子进程返回 0
exit(0);
}
return 0;
}
使用命令gcc -o trial3.out trial3.c -pthread
编译,再使用命令./trial3.out
运行
显示以下结果,可以发现父进程输出了子进程的返回信息 0
3.4 在Linux下利用线程实现“生产者-消费者”同步控制
- 使用数组(10个元素) 代替缓冲区。2个输入线程产生产品(随机数)存到数组中;3个输出线程从数组中取数输出
- Linux使用 互斥锁对象 和 轻量级信号对象 ,主要函数 :sem_wait() ,sem_post() ,pthread_mutex_lock() ,pthread_mutex_unlock()
- 生产者1的数据: 1000-1999(每个数据随机间隔100ms-1s),生产者2的数据: 2000-2999(每个数据随机间隔100ms-1s)
- 消费者每休眠100ms-1s的随机时间消费一个数据
- 屏幕打印(或日志文件记录)每个数据的生产和消费记录
3.4.1 使用信号量
信号量相关函数 在头文件 semaphore.h中
sem_init():动态初始化一个信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
- sem :想要初始化的信号量
- pshared :表明信号量是进程间共享,还是一个进程内的线程共享。若为0,则,一个进程内的线程共享,否则进程间共享。
- value: 信号量初始值
sem_wait : 即P操作
int sem_wait(sem_t *sem);
- sem : 指定操作的信号量
sem_post :即V操作
int sem_post(sem_t *sem);
- sem : 指定操作的信号量
sem_destory :销毁指定信号量
int sem_destroy(sem_t *sem);
- sem : 指定操作的信号量
创建文件(如trial4.c),输入以下代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>
#include<time.h>
#include <errno.h>
#define BUFFER_SIZE 10 // size of buffer
// p_sem : produce semaphore
// indicate that the number of producer threads that can run at the same time
// == unfilled buffer space
// c_sem : consume semaphore
// indicare that the number of consumer threads that can run at the same time
// == filled buffer space
sem_t p_sem, c_sem;
// s is used to achieve mutex
sem_t s;
// declare buffer
int buffer[BUFFER_SIZE];
//
int in =0,out = 0;
int msleep(long msec);
void delay();
void * producer1(void *arg);
void * producer2(void *arg);
void * consumer(void *arg);
int main()
{
// init semaphore p_sem and c_sem
sem_init(&p_sem,0,BUFFER_SIZE);
sem_init(&c_sem,0,0);
// init semaphore s
sem_init(&s,0,1);
srand((unsigned)time(NULL));
pthread_t pro1,pro2;
pthread_t con1,con2,con3;
pthread_create(&pro1,NULL,producer1,NULL);
pthread_create(&pro2,NULL,producer2,NULL);
pthread_create(&con1,NULL,consumer,NULL);
pthread_create(&con2,NULL,consumer,NULL);
pthread_create(&con3,NULL,consumer,NULL);
pthread_join(pro1,NULL);
pthread_join(pro2,NULL);
pthread_join(con1,NULL);
pthread_join(con2,NULL);
pthread_join(con3,NULL);
sem_destroy(&p_sem);
sem_destroy(&c_sem);
sem_destroy(&s);
return 0;
}
// produce(consume) delay
void delay()
{
long interval = rand()%901+100;
msleep(interval);
}
void * producer1(void *arg) //生产者线程
{
//printf("producer1");
int data;
while(1)
{
// printf("producer1xxx");
// produce date
// produce1();
data = rand()%1000 + 1000;
printf("produce data %d\n",data);
sem_wait(&p_sem);//生产信号量减一
sem_wait(&s);
// put the data(v) item into the buffer
buffer[in] = data;
in = (in + 1) % BUFFER_SIZE ;
sem_post(&s);
// consume semaphore plus one
sem_post(&c_sem);
delay();
}
}
void * producer2(void *arg) //生产者线程
{
//printf("producer2\n");
int data;
while(1)
{
//printf("producer2xxx\n");
// produce data
// produce2();
data = rand()%1000 + 2000;
printf("produce data %d\n",data);
sem_wait(&p_sem);//生产信号量减一
sem_wait(&s);
// put the data(v) item into the buffer
buffer[in] = data;
in = (in + 1) % BUFFER_SIZE ;
sem_post(&s);
// consume semaphore plus one
sem_post(&c_sem);
delay();
}
}
void * consumer(void *arg) //消费者线程
{
//printf("consumer");
int data;
while(1)
{
sem_wait(&c_sem); //消费者信号量减一
sem_wait(&s);
data= buffer[out];
out = (out +1) % BUFFER_SIZE;
sem_post(&s);
sem_post(&p_sem);//生产者信号量加一
//consum();
printf("consume data %d\n",data);
delay();
}
}
// sleep thread 'msec' millisecond
int msleep(long msec)
{
struct timespec ts;
int res;
if (msec < 0)
{
// EINVAL : Invalid argument
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
// EINTR: Interrupted function call
// when Interruption of system calls and library functions
return res;
}
使用命令gcc -o trial4.out trial4.c -pthread
编译,再使用命令./trial4.out
运行
3.4.2 使用条件变量+互斥锁
函数pthread_cond_init() : 初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 静态初始化
- cond:想要初始化的条件变量的地址
- attr :指明条件变量的属性。若为NULL,则使用默认属性。
函数pthread_cond_destroy() :销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
- cond: 想要销毁的条件变量的地址
函数pthread_cond_signal: 唤醒一个阻塞在条件变量的线程
int pthread_cond_signal(pthread_cond_t *cond);
- cond: 指定条件变量的地址
函数pthread_cond_wait:阻塞调用该函数的线程,并释放指定互斥锁
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
- cond : 指定条件变量的地址
- mutex : 指定的互斥锁
函数pthread_mutex_init:初始化一个互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化,默认属性
- mutex:想要初始化的互斥锁的地址
- attr :指明互斥锁的属性。若为NULL,则使用默认属性。
函数pthread_mutex_destroy:销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- mutex: 指定互斥锁的地址
函数pthread_mutex_lock:获取锁
函数pthread_mutex_unlock:释放锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- mutex : 指定互斥锁的地址
代码如下
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>
#include<time.h>
#include <errno.h>
#define BUFFER_SIZE 10 // size of buffer
#define PRODUCER_NUM 2
// declare buffer
int buffer[BUFFER_SIZE];
//
int in =0,out = 0;
pthread_mutex_t lock;
pthread_cond_t consume_cond,produce_cond;
int msleep(long msec);
void delay();
void * producer(void *arg);
void * consumer(void *arg);
int main()
{
pthread_mutex_init(&lock, 0);
pthread_cond_init(&consume_cond,0);
pthread_cond_init(&produce_cond,0);
srand((unsigned)time(NULL));
pthread_t pro1,pro2;
pthread_t con1,con2,con3;
int producer_args[PRODUCER_NUM] = {1000,2000};
pthread_create(&pro1,NULL,producer,producer_args);
pthread_create(&pro2,NULL,producer,(producer_args+1));
pthread_create(&con1,NULL,consumer,NULL);
pthread_create(&con2,NULL,consumer,NULL);
pthread_create(&con3,NULL,consumer,NULL);
pthread_join(pro1,NULL);
pthread_join(pro2,NULL);
pthread_join(con1,NULL);
pthread_join(con2,NULL);
pthread_join(con3,NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&consume_cond);
pthread_cond_destroy(&produce_cond);
return 0;
}
// produce(consume) delay
void delay()
{
long interval = rand()%901+100;
msleep(interval);
}
void * producer(void *arg) //生产者线程
{
int range = *(int *) arg;
//printf("producer1");
int data;
while(1)
{
// produce date
// produce1();
data = rand()%1000 + range;
printf("produce data %d\n",data);
pthread_mutex_lock(&lock);
while((in+1)%BUFFER_SIZE == out)
{
pthread_cond_wait(&produce_cond, &lock);
}
// put the data(v) item into the buffer
buffer[in] = data;
in = (in + 1) % BUFFER_SIZE ;
pthread_cond_signal(&consume_cond);
pthread_mutex_unlock(&lock);
delay();
}
}
void * consumer(void *arg) //消费者线程
{
//printf("consumer");
int data;
while(1)
{
// sem_wait(&c_sem); //消费者信号量减一
// sem_wait(&s);
// data= buffer[out];
// out = (out +1) % BUFFER_SIZE;
// sem_post(&s);
// sem_post(&p_sem);//生产者信号量加一
pthread_mutex_lock(&lock);
while(in == out)
{
pthread_cond_wait(&consume_cond,&lock);
}
data= buffer[out];
out = (out +1) % BUFFER_SIZE;
pthread_cond_signal(&produce_cond);
pthread_mutex_unlock(&lock);
//consum();
printf("consume data %d\n",data);
delay();
}
return NULL;
}
// sleep thread 'msec' millisecond
int msleep(long msec)
{
struct timespec ts;
int res;
if (msec < 0)
{
// EINVAL : Invalid argument
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
// EINTR: Interrupted function call
// when Interruption of system calls and library functions
return res;
}
3.5 在Linux下利用信号机制实现进程通信
- 父进程创建子进程,并让子进程进入死循环
- 子进程每隔2秒输出“I am Child Process,alive!\n"
- 父进程询问用户”To terminate Child Process.Yes or No?"
- 若用户回答Y,向子进程发送 用户信号 ,让子进程结束
- 函数:kill(),signal(),利用用户信号,编写信号处理函数
关于信号机制,可参考signale(2) 、signale(7)和Linux 信号(signal)
现在简单说一下信号机制的基本使用
3.5.1 信号注册
signal() 函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum是信号的编号,handler是一个函数指针,就是该线程收到该信号时回调的函数。
3.5.2 信号发送
kill()函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid 是信号的接受者的pid,sig是发送的信号的类型。
3.5.3 信号类型
需要注意的是 SIGKILL 和 SIGSTOP无法被捕捉和忽略。
3.5.4 代码实现
代码如下
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>
void handler(int signum)
{
if(signum == SIGINT)
{
printf("Bye,World!\n");
exit(0);
}
else
{
printf("error signal\n");
}
}
int main(int argc, char *argv[]) {
printf("start program %s, pid: %d, ppid: %d \n", argv[0], getpid(), getppid());
pid_t pid = fork();
if (-1 == pid) {
printf("fork process failed. errno: %u, error: %s\n", errno, strerror(errno));
exit(-1);
}
if (pid == 0) { // child
signal(SIGINT,handler);
while(1)
{
printf("I am Child Process, alive!\n");
sleep(2);
}
} else { // parent
char c;
while(1)
{
printf("To terminate Child Process.Yes(Y) or No(N)?\n");
scanf("\n%c",&c);
if(c == 'N')
{
sleep(2);
}
else if (c == 'Y'){
kill(pid,SIGINT);
break;
}
else {
printf("wrong inout ,please input Y or N\n");
}
}
}
return 0;
}
3.6 在Linux 下模拟哲学家 就餐,提供死锁和非死锁解法
信号量实现的死锁解法
// use semaphore
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>
#include<time.h>
#include <errno.h>
sem_t chopstick[5] ;
// sem_t room ;
int msleep(long msec);
void think(int );
void eat(int );
void * philosppher(void *);
// 哲学家逆时针坐着
int main ()
{
srand((unsigned)time(NULL));
//sem_init(&room,0,4);
for(int i = 0;i < 5;i++)
{
sem_init(chopstick + i,0,1);
}
pthread_t phi_thread[5] ;
int thread_args[5] = {0,1,2,3,4};
for (int i = 0; i < 5; i++)
{
pthread_create(phi_thread+i,NULL,philosppher,thread_args+i);
}
for(int i = 0; i < 5; i++)
{
pthread_join(phi_thread[i],NULL);
}
// sem_destroy(&room);
for(int i = 0;i < 5;i++)
{
sem_destroy(chopstick+i);
}
return 0;
}
void * philosppher (void * arg)
{
int i = *(int *)arg;
while(1)
{
think(i);
// sem_wait(&room); // 一次只允许四位哲学家进入餐厅
sem_wait(chopstick + i); // 取左手边的筷子
sem_wait(chopstick + ((i+1)%5)); // 取右手边的筷子
eat(i);
sem_post(chopstick + ((i+1)%5)); // 放下右手边的筷子
sem_post(chopstick + i); // 放下左手筷子
// sem_post(&room);
}
}
void think(int i)
{
printf("philosopher %d is thinking\n",i);
long interval = rand()%401+100;
msleep(interval);
}
void eat(int i)
{
printf("philosopher %d is eating\n",i);
long interval = rand()%401+100;
msleep(interval);
}
// sleep thread 'msec' millisecond
int msleep(long msec)
{
struct timespec ts;
int res;
if (msec < 0)
{
// EINVAL : Invalid argument
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
// EINTR: Interrupted function call
// when Interruption of system calls and library functions
return res;
}
信号量实现非死锁解法
// use semaphore no deadlock
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>
#include<time.h>
#include <errno.h>
#define N 5
#define LEFT (i-1)%N // i的左邻居编号
#define RIGHT (i+1)%N // i的右邻居编号
#define THINKING 0
#define HUNGRY 1
#define EATING 2
int state[5]; // 记录每一位哲学家的状态
sem_t s[5] ;
sem_t mutex; // 互斥信号量
int msleep(long msec);
void think(int );
void eat(int );
void * philosppher(void *);
void test(int);
void get_chopsticks(int );
void release_chopsticks(int );
// 哲学家逆时针坐着
int main ()
{
srand((unsigned)time(NULL));
//sem_init(&room,0,4);
for(int i = 0;i < 5;i++)
{
sem_init(s + i,0,0);
}
sem_init(&mutex,0,1);
pthread_t phi_thread[5] ;
int thread_args[5] = {0,1,2,3,4};
for (int i = 0; i < 5; i++)
{
pthread_create(phi_thread+i,NULL,philosppher,thread_args+i);
}
for(int i = 0; i < 5; i++)
{
pthread_join(phi_thread[i],NULL);
}
// sem_destroy(&room);
sem_destroy(&mutex);
for(int i = 0;i < 5;i++)
{
sem_destroy(s+i);
}
return 0;
}
void * philosppher (void * arg)
{
int i = *(int *)arg;
while(1)
{
think(i);
get_chopsticks(i);
eat(i);
release_chopsticks(i);
}
}
void get_chopsticks(int i)
{
sem_wait(&mutex); // 进入临界区
state[i] = HUNGRY; // 设置哲学家状态为饥饿
test(i); // 尝试获得两把筷子
sem_post(&mutex); // 出临界区
sem_wait(s+i); // 如果得不到需要的筷子就阻塞
}
void release_chopsticks(int i)
{
sem_wait(&mutex);
state[i] = THINKING;
test(LEFT);
test(RIGHT);
sem_post(&mutex);
}
void think(int i)
{
printf("philosopher %d is thinking\n",i);
long interval = rand()%401+100;
msleep(interval);
}
void eat(int i)
{
printf("philosopher %d is eating\n",i);
long interval = rand()%401+100;
msleep(interval);
}
// sleep thread 'msec' millisecond
int msleep(long msec)
{
struct timespec ts;
int res;
if (msec < 0)
{
// EINVAL : Invalid argument
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
// EINTR: Interrupted function call
// when Interruption of system calls and library functions
return res;
}
void test(int i) {
if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {
state[i] = EATING ;
sem_post(s+i) ;
}
}
条件变量+互斥锁
// use conditonal variable and mutex
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>
#include<time.h>
#include <errno.h>
#include <stdbool.h>
pthread_cond_t ChopstickReady[5]; // 用于同步的条件变量
bool chopstick[5] = {1,1,1,1,1}; // 每个筷子的可用状态
pthread_mutex_t lock;
int msleep(long msec);
void think(int );
void eat(int );
void * philosppher(void *);
void get_chopsticks(int pid);
void release_chopsticks(int pid);
// 哲学家逆时针坐着
int main ()
{
srand((unsigned)time(NULL));
// 初始化互斥锁和条件变量
pthread_mutex_init(&lock,0);
for (int i=0;i<5;i++)
{
pthread_cond_init(ChopstickReady+i,0);
}
pthread_t phi_thread[5] ;
int thread_args[5] = {0,1,2,3,4};
for (int i = 0; i < 5; i++)
{
pthread_create(phi_thread+i,NULL,philosppher,thread_args+i);
}
for(int i = 0; i < 5; i++)
{
pthread_join(phi_thread[i],NULL);
}
pthread_mutex_destroy(&lock);
for (int i=0;i<5;i++)
{
pthread_cond_destroy(ChopstickReady+i);
}
return 0;
}
void * philosppher (void * arg)
{
int i = *(int *)arg;
while(1)
{
think(i); // 思考
get_chopsticks(i); // 拿筷子
eat(i); // 吃饭
release_chopsticks(i); // 放下筷子
}
}
void think(int i)
{
printf("philosopher %d is thinking\n",i);
long interval = rand()%401+100;
msleep(interval);
}
void eat(int i)
{
printf("philosopher %d is eating\n",i);
long interval = rand()%401+100;
msleep(interval);
}
// sleep thread 'msec' millisecond
int msleep(long msec)
{
struct timespec ts;
int res;
if (msec < 0)
{
// EINVAL : Invalid argument
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
// EINTR: Interrupted function call
// when Interruption of system calls and library functions
return res;
}
void get_chopsticks(int pid)
{
int left = pid;
int right = (pid + 1) % 5;
pthread_mutex_lock(&lock);
// 获取左手筷子
while (!chopstick[left])
{
pthread_cond_wait(ChopstickReady + left,&lock);
}
chopstick[left] = false;
// 获取右手筷子
while (!chopstick[right])
{
pthread_cond_wait(ChopstickReady + right,&lock);
}
chopstick[right] = false;
pthread_mutex_unlock(&lock);
}
void release_chopsticks(int pid)
{
int left = pid;
int right = (pid +1) % 5;
pthread_mutex_lock(&lock);
// 释放左手筷子
// 当没有人在等待左手的筷子
if(!(pthread_cond_signal(ChopstickReady + left)))
{
chopstick[left] = true;
}
// 当没有人在等待右手的筷子
if(!(pthread_cond_signal(ChopstickReady + right)))
{
chopstick[right] = true;
}
pthread_mutex_unlock(&lock);
}