【问题标题】:Why are my child processes not giving me the "correct" results?为什么我的子进程没有给我“正确”的结果?
【发布时间】:2015-11-26 21:16:53
【问题描述】:

我的代码的目的是执行两个子进程并增加一个共享变量计数器。每个进程都应将其增加 100 万。 这是我的代码:

    #include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

typedef struct
{
    int value;
}   shared_mem; 
shared_mem  *counter;

//these next two processes are the ones that increment the counter
process1()
{
    int i=0;
    for(i=0;i<1000000;i++)
        counter->value++;
}

process2()
{
    int i=0;
    for(i=0;i<1000000;i++)
        counter->value++;
}

/*  The Main Body   */

main()
{
    key_t   key = IPC_PRIVATE;  /* shared memory key */ 
    int shmid;  /* shared memory ID */ 
    shared_mem  *shmat1;    

    int pid1;   /* process id for child1 */
    int pid2;   /* process id for child2 */


    /* attempts to attach to an existing memory segment */

    if (( shmid = shmget(key, sizeof(int), IPC_CREAT | 0666)) < 0)
    {
        perror("shmget");
         return(1);
    }

    /*attempts the shared memory segment    */

    if((counter = (shared_mem *)shmat(shmid, NULL, 0)) == (shared_mem *) -1)
    {
        perror("shmat");    
        return(1);
    }

    /*initializing shared memory to 0 */ 
    counter->value = 0;

    pid1=fork();
    /* fork process one here */
    if(pid1==0)
    {
        printf("I am child 1 with PID %d\n", getpid());
        process1();
    }
    else
    {
        pid2=fork();

        if(pid2==0)
        {
            printf("I am child 2 with PID %d\n", getpid());
            process2();
        }
        else
        {
            wait(NULL);
            printf("I am parent with PID %d\n", getpid());
            printf("Total counter value is: %d\n", counter->value);
        }

    }


    /*deallocate shared memory */
    if(shmctl(shmid, IPC_RMID, (struct shmid_ds *)0)== -1)
    { 
        perror("shmctl");
        return(-1);
    }
    return(0);

}   

计数器输出在100万左右徘徊,但它不应该在200万左右徘徊吗?我想我不了解流程增量的方式。提前非常感谢,如果代码太长,我深表歉意,但我不确定我可以包含哪些内容以及我可以排除哪些内容。

【问题讨论】:

    标签: c unix fork parent-child increment


    【解决方案1】:

    变量增量本身不是原子的;除非另有说明,否则编译器可以生成如下代码:

    load counter->value in a register
    increment the register
    move the incremented value back to counter->value
    

    这正是 gcc 在禁用优化的情况下生成的那种代码:

    mov rax, QWORD PTR counter[rip]   ; find out the address of counter->value
    mov edx, DWORD PTR [rax]          ; get its content in edx
    add edx, 1                        ; increment edx
    mov DWORD PTR [rax], edx          ; move it back to counter->value
    

    (尽管即使为增量生成的程序集只是一条指令,您也可能会遇到竞争条件 - 例如,即使是 x86 上的直接 inc DWORD PTR[rax] @ 987654321@ 在多核机器上,除非它具有 @ 987654326@前缀。)

    现在,如果您有两个线程不断尝试同时递增变量,大多数情况下您将执行一系列操作,有点类似于:

    Thread A                               Thread B
    load counter->value in a register
                                           load counter->value in a register
                                           increment the register
    increment the register
    move the register to counter->value
                                           move the register to counter->value
    

    由于这两个增量都发生在从相同值开始的单独寄存器中,因此最终结果是counter-&gt;value 似乎只增加了一次,而不是两次(这只是一个可能的示例,您可以设想许多其他可能的可以跳过任意数量的增量的序列 - 认为线程 1 在加载和存储之间暂停,而第二个线程继续进行多次迭代。

    解决方案是使用原子操作来作用于共享值;在 gcc 上,您有几个 atomic builtins 可用,它们扩展为正确的汇编代码,原子地执行所描述的操作,即没有上述交错的风险。

    在这种特殊情况下,您应该将counter-&gt;value++ 替换为__sync_add_and_fetch(&amp;counter-&gt;value, 1)。生成的代码变为

    mov rax, QWORD PTR counter[rip]    ; find out the address of counter->value
    lock add    DWORD PTR [rax], 1     ; atomically increment the pointed value
    

    您应该会看到计数器确实达到了预期的 2000000。

    但请注意,原子操作非常有限,因为 CPU 通常只支持有限数量的类型(通常是小于原生字长的整数)的此类操作,并且只有少数原语可用或易于组装(例如,原子交换、原子比较和交换、原子增量等)。出于这个原因,当您需要保证某个任意代码块始终以原子方式执行时,您必须求助于互斥锁和其他同步原语(它们通常构建在原子操作之上)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-22
      • 1970-01-01
      • 2019-12-05
      • 1970-01-01
      相关资源
      最近更新 更多