【问题标题】:How to make the output order random in process synchronization, semaphore?如何在进程同步,信号量中使输出顺序随机?
【发布时间】:2018-12-15 19:36:06
【问题描述】:
int main() {
    sem_t * sem;

    sem = sem_open("share", O_CREAT, 0 ,1);
    sem_unlink("share");

    int i;
    if (fork()) {      
        for (i=0;i<10;i++) {
            sem_wait(sem);
            display("Hello world\n");
            sem_post(sem);
        }
        wait(NULL);
    } else {
        for (i=0;i<10;i++) {
            sem_wait(sem);
            display("Bonjour monde\n");
            sem_post(sem);
        }

    }   
    return 0; 
}

我正在尝试在进程之间进行同步,它运行良好,但子进程在父进程完成后(10 个 Hello worlds 之后)开始记录 Bonjour monde 文本。输出顺序可以像

那样随机吗
Hello World
Bonjour monde
Bonjour monde
Hello World
....

而不是 10 个 Hello Worlds,然后是 10 个 Bonjour mondes。

【问题讨论】:

  • 听起来您正在寻找缺乏的同步。你需要信号量吗?

标签: c synchronization semaphore


【解决方案1】:

您对“随机”输出的期望似乎比表面上这个词的含义要强烈。就输出顺序而言,您的原始代码确实是不确定的(随机)。从长远来看,这可能会更清楚。它碰巧不喜欢从一个输出切换到另一个输出,因此输出会聚集在一起,但这并不意味着它是非随机的。

由于您也对确定性交替不满意,我想您的意思是每行的每个输出的机会均等,与前一行不相关。在这种情况下,讽刺的是,你需要施加一些控制。例如,您可以调整@CraigEstey 的双信号量解决方案,以便每个进程通过诸如rand() 的伪随机数生成器随机选择要发布到的信号量,而不是每个进程确定性地发布到另一个信号量。我把细节留作练习。

【讨论】:

    【解决方案2】:

    因为你在每次迭代中都在做sem_post(),它会在每次迭代中唤醒所有等待sem的进程,我们不能保证相同的进程会在sem_post()之后取回它所以

    这主要是因为您的机器的调度行为。 您的代码在我的 linux 机器中产生以下输出

    Hello world Bonjour monde Bonjour monde Bonjour monde Bonjour monde Bonjour monde Bonjour monde Bonjour monde Bonjour monde Bonjour monde Hello world Hello world Hello world Hello world Bonjour monde Hello world Hello world Hello world Hello world Hello world

    实际上要保证在 10 条“Bonjor”消息之后获得 10 条“hello”消息,那么我们必须尝试在 child 和 parent 完成 for() 之前和之后调用 sem_wait() sem_post(),而且我们不能保证哪一个会优先安排(父母或孩子都可以先运行)即

    if (fork()) {
        sem_wait(sem);
        for (i = 0; i < 10; i++) {
            display("Hello world\n");
        }
        sem_post(sem);
        wait(NULL);
    }
    

    【讨论】:

    • 这似乎没有抓住问题的重点。 OP 希望避免您的代码产生的情况,一个进程在另一个进程之前产生其所有输出。他希望输出混合(更好)。
    • @JohnBollinger,OP 询问为什么它不是随机发生的,它是随机发生的,但在大多数情况下它不是,输出将取决于在这种情况下进程调度是如何发生的。您可以检查不同机器的 OP 代码,您无法保证该序列,或者如果将其更改为 100 次迭代,则不是 10,如果运行几次,您可以看到随机性,这完全取决于调度行为。但是,如果您按照我的回答修改代码,则可以保证顺序。这就是我的观点。
    • 是的,OP 将他观察到的行为误认为是确定性的,而实际上并非如此。但这并没有改变这样一个事实,即他希望他的两个流程的输出分布更均匀,而您的建议正好相反。
    【解决方案3】:

    你有一个竞争条件。

    当主进程执行sem_post 时,它会[快速] 循环并且[几乎] 立即执行下一个 sem_wait 并在子进程有机会获得信号量之前获取信号量。

    简单/天真的解决方案是在每个循环的底部添加一个小usleep

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <semaphore.h>
    
    int
    main()
    {
        sem_t *sem;
    
        setlinebuf(stdout);
    
        sem = sem_open("share", O_CREAT, 0, 1);
        sem_unlink("share");
    
        int i;
    
        if (fork()) {
            for (i = 0; i < 10; i++) {
                sem_wait(sem);
                printf("Hello world\n");
                sem_post(sem);
                usleep(10);
            }
            wait(NULL);
        }
        else {
            for (i = 0; i < 10; i++) {
                sem_wait(sem);
                printf("Bonjour monde\n");
                sem_post(sem);
                usleep(10);
            }
    
        }
        return 0;
    }
    

    现在的输出是:

    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    Hello world
    Bonjour monde
    

    更新:

    如下所述,两个信号量解决方案更好,因为它保证了交替:

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <semaphore.h>
    
    int
    main()
    {
        sem_t *sempar;
        sem_t *semcld;
    
        setlinebuf(stdout);
    
        sem_unlink("parent");
        sempar = sem_open("parent", O_CREAT, 0, 1);
    
        // by blocking child semaphore, this guarantees parent starts first
        sem_unlink("child");
        semcld = sem_open("child", O_CREAT, 0, 0);
    
        int i;
    
        if (fork()) {
            for (i = 0; i < 10; i++) {
                sem_wait(sempar);
                printf("Hello world (%d)\n",i);
                sem_post(semcld);
            }
            wait(NULL);
        }
    
        else {
            for (i = 0; i < 10; i++) {
                sem_wait(semcld);
                printf("Bonjour monde (%d)\n",i);
                sem_post(sempar);
            }
        }
    
        return 0;
    }
    

    更新 #2:

    但输出不是随机的,它先打印 HelloWorld,然后再打印 Bonjour 并重复。

    好的,那么单信号量解决方案是更好的解决方案,但有一些修改:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <time.h>
    #include <sys/wait.h>
    #include <semaphore.h>
    
    sem_t *sem;
    
    void
    doloop(time_t seed,const char *msg)
    {
        int i;
        int rval;
    
        srand(seed);
    
        for (i = 0; i < 30; i++) {
            rval = rand() % 100000;
            sem_wait(sem);
            printf("%s i=%d rval=%d\n",msg,i,rval);
            sem_post(sem);
            usleep(rval);
        }
    }
    
    int
    main()
    {
        time_t seed;
    
        setlinebuf(stdout);
    
        sem_unlink("share");
        sem = sem_open("share", O_CREAT, 0, 1);
    
        seed = time(NULL);
    
        if (fork()) {
            doloop(seed,"Hello World");
            wait(NULL);
        }
    
        else {
            doloop(seed ^ ~0,"Bonjour monde");
        }
    
        return 0;
    }
    

    【讨论】:

    • 在我的拙见中添加延迟不是答案,这里我们不能保证调度顺序,因为每次迭代 sem_post() 都会被调用,如果我们想要一个接一个地执行子循环或父循环,我们必须在循环完成后执行 sem_post(),因为其他人在执行 sem_post() 之前不会有机会
    • NMDV,但睡眠几乎不是同步问题的正确解决方案。 IMO,最好使用两个信号量来实现进程交替而不需要任何睡眠。
    • @ShivYaragatti 这不是投反对票的理由。 Downvotes 保留用于严重错误答案[根据 SO 指南]。评论更好,因为它让响应者有机会改进他们的答案。而且,您的分析是不正确的,因为解决方案确实 有效,而且我在 之前回答过类似/相同的问题,但从未获得 DVed。做你的建议将工作。你甚至尝试过吗?而且,如果我是你,在你获得更多 SO 经验之前,我会避免 DVing。
    • @JohnBollinger 是的,两个信号量会更好,我打算编写一个并添加它,以显示进度。这就是为什么我说“简单/天真”
    • @CraigEstey 感谢教育我接受我的错误,对不起 DVing,试过但我无法扭转它,不确定你是否可以做到。
    猜你喜欢
    • 2017-08-31
    • 2017-04-10
    • 2020-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-13
    • 1970-01-01
    相关资源
    最近更新 更多