进程管理与死锁

  • 一. 实验目的
  • 二. 实验内容
  • 三. 实验过程
    • 3.1 在Linux 下创建一对父子进程
      • 让父进程提前结束
      • 让父进程后结束
    • 3.2 在Linux下创建两个线程A和B,循环输出数据或字符串
    • 3.3 在Linux下创建父子进程,实验wait同步函数,理解父子进程同步
    • 3.4 在Linux下利用线程实现“生产者-消费者”同步控制
      • 3.4.1 使用信号量
      • 3.4.2 使用条件变量+互斥锁
    • 3.5 在Linux下利用信号机制实现进程通信
      • 3.5.1 信号注册
      • 3.5.2 信号发送
      • 3.5.3 信号类型
      • 3.5.4 代码实现
    • 3.6 在Linux 下模拟哲学家 就餐,提供死锁和非死锁解法
      • 信号量实现的死锁解法
      • 信号量实现非死锁解法
      • 条件变量+互斥锁
  • 参考资料:

一. 实验目的

  1. 理解进程/线程的概念和应用编程过程
  2. 理解进程/线程的同步机制和应用编程

二. 实验内容

  1. 在Linux下创建一队父子进程
  2. 在Linux下创建两个线程A和B,循环输出数据或字符串
  3. 在Linux下创建父子进程,实验wait同步函数,理解父子进程同步
  4. 在Linux下利用线程实现“生产者-消费者”同步控制
  5. 在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());elsefprintf(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 命令查看线程时,该线程没有结束// 睡眠50ssleep(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 // 子进程休眠5ssleep(5);// 子进程返回 0exit(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_semsem_init(&p_sem,0,BUFFER_SIZE);sem_init(&c_sem,0,0);// init semaphore ssem_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 bufferbuffer[in] = data;in = (in + 1) % BUFFER_SIZE ;sem_post(&s);// consume semaphore plus onesem_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 bufferbuffer[in] = data;in = (in + 1) % BUFFER_SIZE ;sem_post(&s);// consume semaphore plus onesem_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 argumenterrno = 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 bufferbuffer[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 argumenterrno = 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 argumenterrno = 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 2int 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 argumenterrno = 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 argumenterrno = 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);}

参考资料:

  • 操原上机作业(二)
  • fork(2)
  • errno(3)
  • linux子进程知道父进程退出的解决方案
  • Solved – Undefined reference to ‘pthread_create’ in Linux with Explanation
  • Is there an alternative sleep function in C to milliseconds?
  • Linux环境下编程(一)——进程fork()的使用
  • pthread_create(3)
  • pthread_join(3)
  • Linux Tutorial:POSIX Threads
  • Linux信号(signal)
  • 哲学家就餐问题

OS实验:进程管理与死锁相关推荐

  1. OS X进程管理之launchctl

    OS X进程管理之launchctl Apple官方文档 如果 Mac 无法完成启动,请尝试安全模式 如果 Mac 无法开机应如何处理 在 Mac OS X 中设置固件密码保护 如何重置 Mac 上的 ...

  2. OS之进程管理 --- 死锁

    什么是死锁 在正常操作模式下,进程按如下顺序来使用资源: 申请:进程请求资源 使用:进程对资源进行操作 释放:进程释放资源 当一组进程中的每一个进程度在等待一个事件,而这事件只能有一组进程的另一个进程 ...

  3. [OS复习]进程管理2

    问题:多个进程竞争内存资源 1.解决方法 方案一:采用交换技术,换出一部分进程到外存,以腾出内存空间 方案二:采用虚拟存储技术,每个进程只能装入一部分程序和数据(存储管理部分) 2.对换技术(交换技术 ...

  4. python中的os abort_Python::OS 模块 -- 进程管理

    这里我们介绍os模块中的进程管理相关的操作. os模块提供给了我们访问操作系统功能的接口,我们可以通过os模块提供给我们的进程管理接口,编写多进程程序,这对编写高效.并发的程序提供了方便. 下面是一个 ...

  5. python os模块进程管理

    2019独角兽企业重金招聘Python工程师标准>>> 有两种方式来实现并发性,一种方式是让每个"任务"或"进程"在单独的内在空间中工作,每个 ...

  6. [OS复习]进程管理5

    线程 1.多线程 操作系统中引入进程的目的: 为了描述和实现多个程序的并发执行,以改善资源利用率及提高系统的吞吐量. 操作系统引入线程的目的: 这是为了减少程序并发执行时系统所付出的额外开销(减少管理 ...

  7. [OS复习]进程管理4

    进程调度算法(Short-Term) 1.先来先服务(FCFS) 该方法按照进程到达的先后顺序排队,每次调度队首的进程(就像超市中购物付款一样). FCFS算法属于非剥夺调度方式,实现简单,看似公平. ...

  8. Python之OS模块进程管理介绍--os.fork()

    转自:http://davidbj.blog.51cto.com/4159484/1240586 有两种方式来实现并发性,一种方式是让每个"任务"或"进程"在单 ...

  9. 计算机实验进程管理与虚拟机,虚拟机VMware进程控制实验.docx

    虚拟机VMware进程控制实验 实验6:进程控制操作 1.实验目的 1.了解进程的概念: 2.熟悉Linux的前台与后台进程控制操作: 3.掌握利用进程监控工具来维护系统的正常运行: 2.实验内容 1 ...

最新文章

  1. Magento开发的特点有哪些?
  2. 梯度下降(Gradient Descent),一句代码,一个式子
  3. 回调函数案列(C高级)
  4. flstudio插件找不到_Eclipse4.17安装spring插件的问题
  5. 【kafka】kafka 错误代码解释
  6. AI 如何应用于油气勘探?
  7. 精通python工资高吗-软件测试,如何工资过万?
  8. burpsuite_pro的使用
  9. 电脑ps计算机磨皮,ps磨皮教程
  10. pm2.5计算和单位换算
  11. 2021年P气瓶充装新版试题及P气瓶充装证考试
  12. 给计算机图片文件夹加密码,文件夹怎么设置密码
  13. pl sql迁移oracle,Oracle数据库安装及使用PLSQL数据迁移
  14. python----引用其他py文件中的函数
  15. Telerik Silverlight 之Charting控件的使用
  16. ios pushViewController 页面不跳转问题解决
  17. android自定义手势,Android编程实现自定义手势的方法详解
  18. solidworks批量图号分离_SolidWorks如何利用宏来快速的实现 “图号名称”分离 呢?...
  19. matlab 回归分析箱,RegrToolbox5 matlab回归分析工具箱 - 下载 - 搜珍网
  20. 计算机本地连接xp,xp本地连接不见了怎么办【图解】

热门文章

  1. Cosmos 白皮书
  2. AXURE手机版注册登录原型(下载+教学)
  3. Talk预告 | 微软亚洲研究院王希廷:基于逻辑规则推理的深度自可解释模型
  4. 核电站问题(简单DP)
  5. c语言switch顺序,switch-case的执行顺序,该怎么处理
  6. Excel在统计分析中的应用—第二章—描述性统计-分组数据的中位数的求解方法(组离散数据)
  7. 破解Kotlin协程创建调用的那些事
  8. AutoCAD Civil 3D-曲面-2、曲面的样式
  9. Numpy删除指定行
  10. java 内部接口 内部类_Java接口/内部类