优雅地退出无限循环线程

Ale*_*lex 7 c++ multithreading

我一直遇到试图运行具有以下属性的线程的问题:

  1. 在无限循环中运行,检查一些外部资源,例如来自网络或设备的数据,
  2. 迅速从其资源获取更新,
  3. 当被要求时,及时退出,
  4. 有效地使用CPU.

第一种方法

我见过的一个解决方案如下:

void class::run()
{
    while(!exit_flag)
    {
        if (resource_ready)
            use_resource();
    }
}
Run Code Online (Sandbox Code Playgroud)

这满足第1,2和3点,但是忙碌的等待循环使用100%CPU.

第二种方法

一个潜在的解决方法是将睡眠声明放入:

void class::run()
{
    while(!exit_flag)
    {
        if (resource_ready)
            use_resource();
        else
            sleep(a_short_while);
    }
}
Run Code Online (Sandbox Code Playgroud)

我们现在不打击CPU,所以我们解决了1和4,但是a_short_while当资源准备好或者我们被要求退出时,我们可以不必要地等待.

第三种方法

第三种选择是对资源进行阻塞读取:

void class::run()
{
    while(!exit_flag)
    {
        obtain_resource();
        use_resource();
    }
}
Run Code Online (Sandbox Code Playgroud)

这将优雅地满足1,2和4,但是现在如果资源不可用,我们不能要求线程退出.

只要能够实现CPU使用率和响应性之间的权衡,最好的方法似乎是第二个,睡眠时间短.然而,这似乎仍然不是最理想的,对我来说也不优雅.这似乎是一个常见的问题需要解决.有更优雅的方式来解决它吗?有没有一种方法可以满足所有这四个要求?

Ada*_*eld 8

这取决于线程正在访问的资源的细节,但基本上以最小的延迟有效地执行它,资源需要提供用于执行可中断阻塞等待的API.

在POSIX系统上,如果您使用的资源是文件或文件描述符(包括套接字),则可以使用select(2)poll(2)系统调用来执行此操作.为了允许等待被抢占,您还可以创建一个可以写入的虚拟管道.

例如,以下是如何等待文件描述符或套接字准备就绪或代码被中断的方法:

// Dummy pipe used for sending interrupt message
int interrupt_pipe[2];
int should_exit = 0;

void class::run()
{
    // Set up the interrupt pipe
    if (pipe(interrupt_pipe) != 0)
        ;  // Handle error

    int fd = ...;  // File descriptor or socket etc.
    while (!should_exit)
    {
        // Set up a file descriptor set with fd and the read end of the dummy
        // pipe in it
        fd_set fds;
        FD_CLR(&fds);
        FD_SET(fd, &fds);
        FD_SET(interrupt_pipe[1], &fds);
        int maxfd = max(fd, interrupt_pipe[1]);

        // Wait until one of the file descriptors is ready to be read
        int num_ready = select(maxfd + 1, &fds, NULL, NULL, NULL);
        if (num_ready == -1)
            ; // Handle error

        if (FD_ISSET(fd, &fds))
        {
            // fd can now be read/recv'ed from without blocking
            read(fd, ...);
        }
    }
}

void class::interrupt()
{
    should_exit = 1;

    // Send a dummy message to the pipe to wake up the select() call
    char msg = 0;
    write(interrupt_pipe[0], &msg, 1);
}

class::~class()
{
    // Clean up pipe etc.
    close(interrupt_pipe[0]);
    close(interrupt_pipe[1]);
}
Run Code Online (Sandbox Code Playgroud)

如果你在Windows上,该select()函数仍适用于套接字,但适用于套接字,因此您应该安装use WaitForMultipleObjects来等待资源句柄和事件句柄.例如:

// Event used for sending interrupt message
HANDLE interrupt_event;
int should_exit = 0;

void class::run()
{
    // Set up the interrupt event as an auto-reset event
    interrupt_event = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (interrupt_event == NULL)
        ;  // Handle error

    HANDLE resource = ...;  // File or resource handle etc.
    while (!should_exit)
    {
        // Wait until one of the handles becomes signaled
        HANDLE handles[2] = {resource, interrupt_event};
        int which_ready = WaitForMultipleObjects(2, handles, FALSE, INFINITE);    
        if (which_ready == WAIT_FAILED)
            ; // Handle error
        else if (which_ready == WAIT_OBJECT_0))
        {
            // resource can now be read from without blocking
            ReadFile(resource, ...);
        }
    }
}

void class::interrupt()
{
    // Signal the event to wake up the waiting thread
    should_exit = 1;
    SetEvent(interrupt_event);
}

class::~class()
{
    // Clean up event etc.
    CloseHandle(interrupt_event);
}
Run Code Online (Sandbox Code Playgroud)


sth*_*sth 3

obtain_ressource()如果您的函数支持超时值,您将获得有效的解决方案:

while(!exit_flag)
{
    obtain_resource_with_timeout(a_short_while);
    if (resource_ready)
        use_resource();
}
Run Code Online (Sandbox Code Playgroud)

这有效地将sleep()obtain_ressurce()调用结合起来。