【操作系统】2.3_11_ 哲学家进餐问题

https://www.bilibili.com/video/BV1YE411D7nH?spm_id_from=333.788.videopod.episodes&vd_source=6c2daed6731190bb7d70296d6b9746bb&p=36

方法1

n个哲学家,n个筷子

创建一个初值为n-1的信号量,保证最多只有n-1个进程并发争抢资源,必有1个筷子资源余留,可以1个进程拿到两支筷子,不会死锁。

这个方法的原子操作是拿取一个筷子的过程

#include <iostream>
#include <semaphore.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <fcntl.h>

#define N 5

using namespace std;

int main(){
    int n = N;
    pid_t pid;

    /************* 信号量 *************/

    //创建信号量,初值n-1;
    char sem_name[20] = "/my_semaphore";
    sem_t* sem = sem_open(sem_name, O_CREAT|O_EXCL, 0666, n-1);
    if (sem == SEM_FAILED){
        if (errno == EEXIST)
        {
            // 如果信号量已存在,尝试删除它
            sem_unlink(sem_name);
            // 等待sem_unlink完成,避免竞态条件
            while (sem_open(sem_name, O_EXCL, 0666, n-1 )!= SEM_FAILED) {
                usleep(1000); // 等待1毫秒
            }
            sem = sem_open(sem_name, O_CREAT|O_EXCL, 0666, n-1);
        }
    }

    /************ 使用信号量保证拿取的原子性 ************/
    sem_t* sems[N]; // 为每个筷子创建互斥量
    for (int i = 0; i < n; i++) {
        sprintf(sem_name, "/sem_%d", i);
        sems[i] = sem_open(sems_name[i], O_CREAT | O_EXCL, 0666, 1);
        if (sems[i] == SEM_FAILED){
            if (errno == EEXIST)
            {
                // 如果信号量已存在,尝试删除它
                sem_unlink(sems_name[i]);
                // 等待sem_unlink完成,避免竞态条件
                while (sem_open(sems_name[i], O_EXCL, 0666, n-1 )!= SEM_FAILED) {
                    usleep(1000); // 等待1毫秒
                }
                sems[i] = sem_open(sems_name[i], O_CREAT|O_EXCL, 0666, 1);
            }
        }
    }

    /********** 哲学家子进程 ***********/
    int i;

    //创建n个哲学家子进程
    for (i = 0; i < n; i++){
        pid = fork();
        if(pid == 0)
            break;
        if (pid < 0)
            perror("fork");
    }

    // 哲学家子进程
    if(pid == 0){

        // 使用信号量取得进餐权限
        sem_wait(sem);

        // 左右筷子编号
        int l = i;
        int r = (i+1)%n;

        // 等待筷子
        sem_wait(sems[l]);
        sem_wait(sems[r]);

        // 模拟进餐
        sleep(1);

        cout << "哲学家 " << i << " 号进餐完毕" << endl;

        // 释放筷子
        sem_post(sems[l]);
        sem_post(sems[r]);

        //释放信号量
        sem_post(sem);

        // 关闭信号量
        if (sem_close(sem) == -1) {
            perror("sem_close");
            exit(1);
        }

        exit(0);
    }

    // 子进程回收
    while (wait(NULL) > 0);
    cout << "哲学家已都进餐完毕" << endl;

    /************* 信号量回收 *************/

    // 完成后关闭信号量
    if (sem_close(sem) == -1) {
        perror("sem_close");
        exit(EXIT_FAILURE);
    }

    // 删除信号量(可选,如果不再需要)
    if (sem_unlink(sem_name) == -1) {
        perror("sem_unlink");
        exit(EXIT_FAILURE);
    }

    // 关闭和删除信号量
    for (int i = 0; i < n; i++) {
        sprintf(sem_name, "/sem_%d", i);
        if (sem_close(sems[i]) == -1) {
            perror("sem_close");
        }
        if (sem_unlink(sem_name) == -1) {
            perror("sem_unlink");
        }
    }

    return 0;
}

方法2

#include <iostream>
#include <semaphore.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <fcntl.h>

#define N 5

using namespace std;

int main(){
    int n = N;
    pid_t pid;
    char sems_name[N][20];

    /************ 使用信号量保证拿取的原子性 ************/
    sem_t* sems[N]; // 为每个筷子创建互斥量
    for (int i = 0; i < n; i++) {
        sprintf(sems_name[i], "/sem_%d", i);
        sems[i] = sem_open(sems_name[i], O_CREAT | O_EXCL, 0666, 1);
        if (sems[i] == SEM_FAILED){
            if (errno == EEXIST)
            {
                // 如果信号量已存在,尝试删除它
                sem_unlink(sems_name[i]);
                // 等待sem_unlink完成,避免竞态条件
                while (sem_open(sems_name[i], O_EXCL, 0666, n-1 )!= SEM_FAILED) {
                    usleep(1000); // 等待1毫秒
                }
                sems[i] = sem_open(sems_name[i], O_CREAT|O_EXCL, 0666, 1);
            }
        }
    }

    /********** 哲学家子进程 ***********/
    int i;

    //创建n个哲学家子进程
    for (i = 0; i < n; i++){
        pid = fork();
        if(pid == 0)
            break;
        if (pid < 0)
            perror("fork");
    }

    // 哲学家子进程
    if(pid == 0){

        // 左右筷子编号
        int l = i;
        int r = (i+1)%n;

        // 等待筷子
        if(i%2==0){
            sem_wait(sems[l]);
            sem_wait(sems[r]);
        }else{
            sem_wait(sems[r]);
            sem_wait(sems[l]);
        }

        // 模拟进餐
        sleep(1);

        cout << "哲学家 " << i << " 号进餐完毕" << endl;

        // 释放筷子
        sem_post(sems[l]);
        sem_post(sems[r]);

        if (sem_close(sems[i]) == -1) {
            perror("sem_close");
            exit(1);
        }

        exit(0);
    }

    // 子进程回收
    while (wait(NULL) > 0);

    cout << "哲学家已都进餐完毕" << endl;

    /************* 信号量回收 *************/

    for (int i = 0; i < n; i++) {
        if (sem_close(sems[i]) == -1) {
            perror("sem_close");
        }
        if (sem_unlink(sems_name[i]) == -1) {
            perror("sem_unlink");
        }
    }

    return 0;
}

方法3

对拿两个筷子这一整个过程进行互斥操作,保证总有一个进程完整的拿到两个筷子。

#include <iostream>
#include <semaphore.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <fcntl.h>

#define N 5

using namespace std;

int main(){
    int n = N;
    pid_t pid;
    const char* sem_name = "/my_semaphore";
    char sems_name[N][20];

    /************* 信号量 *************/

    //创建 拿一双筷子这个过程 的互斥量
    sem_t* sem = sem_open(sem_name, O_CREAT|O_EXCL, 0666, 1);
    if (sem == SEM_FAILED){
        if (errno == EEXIST)
        {
            // 如果信号量已存在,尝试删除它
            sem_unlink(sem_name);
            // 等待sem_unlink完成,避免竞态条件
            while (sem_open(sem_name, O_EXCL, 0666, n-1 )!= SEM_FAILED) {
                usleep(1000); // 等待1毫秒
            }
            sem = sem_open(sem_name, O_CREAT|O_EXCL, 0666, n-1);
        }
    }

    /************ 使用信号量保证拿取的原子性 ************/
    sem_t* sems[N]; // 为每个筷子创建互斥量
    for (int i = 0; i < n; i++) {
        sprintf(sems_name[i], "/sem_%d", i);
        sems[i] = sem_open(sems_name[i], O_CREAT | O_EXCL, 0666, 1);
        if (sems[i] == SEM_FAILED){
            if (errno == EEXIST)
            {
                // 如果信号量已存在,尝试删除它
                sem_unlink(sem_name);
                // 等待sem_unlink完成,避免竞态条件
                while (sem_open(sem_name, O_EXCL, 0666, n-1 )!= SEM_FAILED) {
                    usleep(1000); // 等待1毫秒
                }
                sems[i] = sem_open(sem_name, O_CREAT|O_EXCL, 0666, 1);
            }
        }
    }

    /********** 哲学家子进程 ***********/
    int i;

    //创建n个哲学家子进程
    for (i = 0; i < n; i++){
        pid = fork();
        if(pid == 0)
            break;
        if (pid < 0)
            perror("fork");
    }

    // 哲学家子进程
    if(pid == 0){

        // 使用信号量取得拿一双筷子权限
        sem_wait(sem);

        // 左右筷子编号
        int l = i;
        int r = (i+1)%n;

        // 等待筷子
        sem_wait(sems[l]);
        sem_wait(sems[r]);

        // 模拟进餐
        sleep(1);

        cout << "哲学家 " << i << " 号进餐完毕" << endl;

        // 释放筷子
        sem_post(sems[l]);
        sem_post(sems[r]);

        //释放拿一双筷子这个过程 的信号量
        sem_post(sem);

        // 子进程关闭信号量
        if (sem_close(sem) == -1) {
            perror("sem_close");
            exit(1);
        }
        if (sem_close(sems[i]) == -1) {
            perror("sem_close");
            exit(1);
        }

        exit(0);
    }

    // 子进程回收
    while (wait(NULL) > 0);
    cout << "哲学家已都进餐完毕" << endl;

    /************* 信号量回收 *************/

    // 完成后关闭信号量
    if (sem_close(sem) == -1) {
        perror("sem_close");
        exit(EXIT_FAILURE);
    }
    if (sem_unlink(sem_name) == -1) {
        perror("sem_unlink");
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < n; i++) {
        if (sem_close(sems[i]) == -1) {
            perror("sem_close");
        }
        if (sem_unlink(sems_name[i]) == -1) {
            perror("sem_unlink");
        }
    }

    return 0;
}
posted @ 2024-11-26 15:45  丘狸尾  阅读(48)  评论(0)    收藏  举报