OS实验:进程管理与死锁

本文详细介绍了Linux下的进程管理、线程创建、父子进程同步、生产者-消费者模型、信号机制、哲学家就餐问题以及死锁解决方案。通过实例演示了wait、信号量、条件变量等同步机制,帮助读者深入理解并发编程技术。
摘要由CSDN通过智能技术生成

一. 实验目的

  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());
    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 LinuxCompiling 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);

}

参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值