【问题标题】:Terminating blocking IO in Linux C++在 Linux C++ 中终止阻塞 IO
【发布时间】:2012-06-05 18:16:40
【问题描述】:

我正在使用多线程在 c++ 中的 linux 上进行串行 IO。目前我正在使用阻塞读取。这让我无法停止阻塞 read() 中的线程,除非强制终止或中断线程或使用诸如 pthread 取消之类的东西。现在在整个网络上,我看到人们对人们尖叫,建议他们需要从阻塞的 IO 中终止线程。通常它与内存泄漏有关。只要您正确清理,线程中断是否会出现一些神奇的内存泄漏?

try
{
    while(true)
    {
        blocking_read(fd,buffer,512);
    }
}catch(interrupt_exception)
{

}
//clean up, close fd, release heap memory, usual stuff

或者是我唯一的替代方案,如下所示,或者实现更高级别的协议,以确保阻塞读取接收到签名输入,使其能够自行关闭。

try
{
    while(running)
    {
        nonblocking_read(fd,buffer,512);

        if(cancel)
            running = false; //break return etc
    }
}

//clean up, close fd, release heap memory, usual stuff

同样,如果你中断线程导致它抛出异常,在 read() 中是否会发生一些神奇的内存泄漏。

或者我应该根本不在乎并让析构函数杀死线程(我假设当您删除持有线程的对象时线程终止)?并在那里清理?喜欢

class MyClass{
    int fd;    
    Thread* myThread;
    ~MyClass(){
        delete myThread;
        close(fd);
    }
};

感谢您的帮助!

【问题讨论】:

    标签: c++ linux multithreading io


    【解决方案1】:

    pthread_cancel 不会直接导致神奇的内存泄漏。如果您的线程希望在明确定义的取消点处停止(有关更多详细信息,请参阅 pthread_cancel 手册页),那么可以考虑到这一点进行设计。

    以下测试程序显示 valgrind 没有错误,但是请注意,对于 glibc 实现,依赖于线程被取消时生成的异常是不可移植的(请参阅Cancellation and C++ Exceptions )。

    -尼克

    #include <cstdio>
    #include <pthread.h>
    #include <unistd.h>
    
    void* thread_func(void*)
    {
        try
        {
            char buf[2048];
            read(1, buf, 2048);
        }
        catch(...)
        {
            fprintf(stderr, "thread %lu cancelled\n", pthread_self());
            throw;
        }
        return NULL;
    }
    
    int main()
    {
        pthread_t thread;
        int res =  pthread_create(&thread, NULL, thread_func, NULL);
        sleep(1);
        pthread_cancel(thread);
        void* tres;
        pthread_join(thread, &tres);
        return 0;
    }
    

    【讨论】:

    • 我应该补充一点,非阻塞 IO 几乎总是更好,但是 pthread_cancel 的简单性使其在某些情况下是合适的。
    【解决方案2】:

    read() 不应泄漏内存。对于阻塞和非阻塞读取,应用程序代码仍然负责管理作为buf 参数提供的内存。通过信号中断read()不会抛出异常,所以如果使用信号,则需要检查结果和errno

    • 如果read() 在读取数据之前被信号中断,将返回-1,errno 设置为EINTR
    • 如果read() 在读取一些数据后被信号中断,POSIX 允许返回 -1 并将 errno 设置为 EINTR,或允许 read() 返回数字已读取的字节数。

    如果您使用pthread_cancel(),则会引发异常。使用这种方法,您有以下选择:

    • 通过向pthread_cleanup_push() 注册清理函数来执行清理。
    • 分配动态内存,通过pthread_setspecific()存储到线程特定的存储中。
    • 通过auto_ptr/unique_ptr管理内存。
    • 捕获abi::__forced_unwind 异常,执行清理并重新抛出。

    一般来说,请考虑避免线程取消。在可能的情况下,最好使用一个用于跳出循环的共享标志,并且更易于管理。这允许线程执行任何必要的清理,并防止您的实现依赖于线程库的实现及其任何怪癖。

    对于您使用阻塞读取的情况,请考虑轮询 fd 以查看是否可以通过 select() 使用超时来获取数据,并且仅在 fd 有数据时才调用 read()。这允许您定期检查线程标志是否设置为不再运行,并防止您需要处理信号以中断来自read() 的线程,因为read() 不应再阻止等待数据。

    此外,删除线程对象时发生的行为取决于线程库。例如,删除pthread_tboost::thread 对关联线程的执行没有影响。

    【讨论】:

    • 非常实用和有用的答案。我已经在我工作过的路由器相关项目中看到了这个答案的应用。感谢您提供很多选择
    【解决方案3】:

    我真的只是向那个线程发送一个信号,这就是信号的用途。在您的处理程序中将您的全局running 设置为false,并在您的第二个sn-p 中设置一个while(running) 循环,就是这样。

    read 不会泄漏,它会读入您已经分配并且您知道的缓冲区。它要么成功,要么失败(或阻塞,但信号会解除阻塞)。如果它成功了,看看你能对数据做什么(它可能仍然是部分读取或损坏或其他),否则重复直到runningfalse

    然后清理,释放您分配的内容,然后优雅地退出线程(...或做任何您想做的事情)。

    如果我没记错的话,在不提供SA_RESTART 的情况下安装处理程序时,根本不会出现关于重新启动系统调用的相当复杂的“接收一些数据之前和之后”的情况。
    但即便如此,如果确实出现了,谁在乎 - 最后,系统调用只能失败或成功,read 即使在没有信号的情况下也可以总是返回部分数据,所以你无论如何,必须始终为此做好准备。因此,您真的只关心您是否已经收集了足够的数据以使其有用,或者running 标志是否由于某种原因“神奇地”变为false(这个原因将是您的信号处理程序) .

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-24
      • 2010-09-23
      • 2012-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多