首页 > 解决方案 > __sync_fetch_and_add() 的信号安全

问题描述

我有一个接受套接字客户端并分叉子进程的程序。为了保持子进程的计数,我使用了一个全局变量,原子函数用于递增和递减计数。

我担心 __sync_fetch_and_add() 的信号安全。当多个孩子频繁连接和断开时,是否存在死锁的可能性。

下面是我的代码片段。

   int main()
   {
            ...
            signal(SIGCHLD, handle_sigchild);
            ...
            if ((pid = fork()) == -1) {
                close(sock);
                continue;
            } else if (pid > 0) { //Parent
                __sync_fetch_and_add(&counter, 1);
                close(sock);
                continue;
            } else if (pid == 0) {
            ...
            }
        ...
    }
    
    // Signal Handler
    void handle_sigchild(int arg)
    {
        while( waitpid(-1, NULL, WNOHANG) > 0){
            __sync_fetch_and_add(&counter, -1);
        }
        signal(SIGCHLD, handle_sigchild);
    }

标签: cmultithreadingsignalsatomic

解决方案


原子函数通常实现为围绕执行实际操作的关键指令的循环,在这种情况下可能会失败,需要重试。

任何冲突都会导致关键指令失败,这也适用于信号处理程序引入的冲突。

例如,fetch-and-add通常类似于

.L2:
    lwarx 10,0,3
    addi 9,10,1
    stwcx. 9,0,3
    bne 0,.L2
    extsw 3,10
    blr

因此,加载该值并进行预订,然后递增,然后如果预订仍然有效,则将其存储回来。如果失败,我们重新开始获取新值(因为很可能有人在此期间写过)。

在 Intel 上,这个过程隐藏在里面

    mov     eax, 1
    lock xadd       DWORD PTR [rdi], eax
    ret

在旧机器上,这确实会在 add 指令期间锁定整个总线,在较新的机器上,它会默默地在微码中生成一个循环,反复尝试增加值。

任何中断(传递信号所必需的)只会延长加载和存储之间的时间,这使得当前迭代更有可能无法更新值,因为其他东西更快。


推荐阅读