这是从信号处理程序返回值的可靠方法吗?

时间:2015-03-21 13:42:01

标签: c++ linux multithreading signals posix

我想收集终止子流程的pid,我在与信号处理程序通信时遇到问题...我没有c(++)11。 这有效吗?还请考虑我的程序将是多线程的(但队列只能在一个线程中使用)。

#include <iostream>
#include <csignal>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/wait.h>
template<typename _ValueType, int _Capacity>
class signal_result_queue
{
public:
    static const int capacity = _Capacity;
    typedef _ValueType value_type;
private:
    //+1 so we can distinguish empty from full
    volatile value_type _data[capacity+1];
    volatile sig_atomic_t _base;
    volatile sig_atomic_t _next;
    volatile sig_atomic_t _overflows;
public:
    signal_result_queue()
    : _base(0)
    , _next(0)
    , _overflows(0)
    {}

    void push_sh(value_type value)
    {
        int base = _base; // we are not interrupted anyway
        int next = _next; // same here
        int before_base = wrap(base-1);
        if(next == before_base)
        {
            ++_overflows;
        }
        else
        {
            _data[next] = value;
            _next = wrap(next+1);
        }
    }

    int overflows_t1()
    {
        int overflows = _overflows;
        _overflows -= overflows;
        return overflows;
    }

    bool pop_t1(value_type& result)
    {
        // this is only changed by us.
        int base = _base;
        // It might increase but no problem
        int next = _next;

        if(base == next)
        {
            return false;
        }
        else
        {
            result = _data[base];
            _base = wrap(base+1);
            return true;
        }
    }
private:
    static int wrap( int i )
    {
        while(i>=capacity+1)
        {
            i-=(capacity+1);
        }
        while(i<0)
        {
            i+=(capacity+1);
        }
        return i;
    }
};

signal_result_queue<pid_t, 20> q;

void sigchld_handler(int)
{
    pid_t pid;
    while((pid = waitpid(static_cast<pid_t>(-1), 0, WNOHANG)) > 0){
        q.push_sh(pid);
    }
}

int main()
{
    struct sigaction sa;
    sa.sa_handler = &sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
    if (sigaction(SIGCHLD, &sa, 0) == -1) {
      perror(0);
      std::exit(1);
    }

    for(int i=0;i<10;i++)
    {
        pid_t pid = fork();
        if(pid == 0) // child
        {
            sleep(i);
            return 0;
        }
        else if(pid == -1) // error
        {
            std::cout << "fork error" << std::endl;
        }
        else // parent
        {
            std::cout << "fork pid: " << pid <<  std::endl;
        }
    }
    int terminated = 0;
    do
    {
        sleep(1);
        int overflows = q.overflows_t1();
        if(overflows > 0)
        {
            std::cout << "Overflow in queue:" << overflows << std::endl;
        }
        pid_t val;
        while(q.pop_t1(val))
        {
            terminated++;
            std::cout << "Terminated: " << val <<  std::endl;
        }
    } while(terminated < 10);
    return 0;
}

2 个答案:

答案 0 :(得分:0)

由于两个原因,这不是一种安全的方法:

  • volatile不足以避免多线程程序中的竞速条件。 sig_atomic_t保证是信号重新存在的原子,但我怀疑它对多线程是否足够。
  • 全局队列变量的使用不符合信号处理程序的重新连接要求。

使用C ++ 11,您可以使用atomic而不是volatile。如果没有C ++ 11,您需要通过mutex lock保护访问共享变量来保护对队列的并发访问。这将解决上述两个问题。

如有任何疑问,请进行补充演示:

只是为了更详细地说明它是多么不安全:

假设处理了第一个过程信号:

    int base = _base; // acces to _base is atomic. It's protected ONLY DURING THE STATEMENT
    int next = _next; // same here
    int before_base = wrap(base-1);

现在想象另一个线程调用处理程序,并在CPU上运行。为简单起见,假设第一个被系统调度程序搁置。所以处理程序的第二个实例执行:

    int base = _base; // SAME BASE IS STORED LOCALLY  
    int next = _next; // SAME NEXT IS STORED LOCALLY 
    int before_base = wrap(base-1);

所以此时,处理程序的两个实例在basenext的本地副本中都有相同的索引。现在第二个实例继续:

        ...
        _data[next] = value;   // value is stored in the queue
        _next = wrap(next+1);  // and _next is incremented.  

这里调度程序再次唤醒第一个实例,它会立即继续:

        _data[next] = value;   // USES ITS LOCAL COPY OF NEXT, WRITE IN SAME PLACE THAN THE OTHER INSTANCE !!
        _next = wrap(next+1);  // and _next is incremented BASED ON LOCAL COPY.  

所以此时,在您的队列中,您只应该存储两个值中的一个。

答案 1 :(得分:0)

我的朋友解决了这个问题,他只在信号发生时增加了一个不稳定的sig_atomic_t,并在MAIN线程中使用了waitpid。 这很简单。我看到一个代码在信号处理程序中调用waitpid,我没有意识到它可以在主线程中稍后调用。 不需要这个队列。