Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exit an infinite looping thread elegantly

I keep running into this problem of trying to run a thread with the following properties:

  1. runs in an infinite loop, checking some external resource, e.g. data from the network or a device,
  2. gets updates from its resource promptly,
  3. exits promptly when asked to,
  4. uses the CPU efficiently.

First approach

One solution I have seen for this is something like the following:

void class::run()
{
    while(!exit_flag)
    {
        if (resource_ready)
            use_resource();
    }
}

This satisfies points 1, 2 and 3, but being a busy waiting loop, uses 100% CPU.

Second approach

A potential fix for this is to put a sleep statement in:

void class::run()
{
    while(!exit_flag)
    {
        if (resource_ready)
            use_resource();
        else
            sleep(a_short_while);
    }
}

We now don't hammer the CPU, so we address 1 and 4, but we could wait up to a_short_while unnecessarily when the resource is ready or we are asked to quit.

Third approach

A third option is to do a blocking read on the resource:

void class::run()
{
    while(!exit_flag)
    {
        obtain_resource();
        use_resource();
    }
}

This will satisfy 1, 2, and 4 elegantly, but now we can't ask the thread to quit if the resource does not become available.

Question

The best approach seems to be the second one, with a short sleep, so long as the tradeoff between CPU usage and responsiveness can be achieved. However, this still seems suboptimal, and inelegant to me. This seems like it would be a common problem to solve. Is there a more elegant way to solve it? Is there an approach which can address all four of those requirements?

like image 916
Alex Avatar asked Oct 10 '13 00:10

Alex


Video Answer


4 Answers

This depends on the specifics of the resources the thread is accessing, but basically to do it efficiently with minimal latency, the resources need to provide an API for either doing an interruptible blocking wait.

On POSIX systems, you can use the select(2) or poll(2) system calls to do that, if the resources you're using are files or file descriptors (including sockets). To allow the wait to be preempted, you also create a dummy pipe which you can write to.

For example, here's how you might wait for a file descriptor or socket to become ready or for the code to be interrupted:

// 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]);
}

If you're on Windows, the select() function still works for sockets, but only for sockets, so you should install use WaitForMultipleObjects to wait on a resource handle and an event handle. For example:

// 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);
}
like image 164
Adam Rosenfield Avatar answered Oct 16 '22 12:10

Adam Rosenfield


You get a efficient solution if your obtain_ressource() function supports a timeout value:

while(!exit_flag)
{
    obtain_resource_with_timeout(a_short_while);
    if (resource_ready)
        use_resource();
}

This effectively combines the sleep() with the obtain_ressurce() call.

like image 38
sth Avatar answered Oct 16 '22 12:10

sth


Check out the manpage for nanosleep:

If the nanosleep() function returns because it has been interrupted by a signal, the function returns a value of -1 and sets errno to indicate the interruption.

In other words, you can interrupt sleeping threads by sending a signal (the sleep manpage says something similar). This means you can use your 2nd approach, and use an interrupt to immediately wake the thread if it's sleeping.

like image 34
DaoWen Avatar answered Oct 16 '22 11:10

DaoWen


Use the Gang of Four Observer Pattern:

http://home.comcast.net/~codewrangler/tech_info/patterns_code.html#Observer

Callback, don't block.

like image 25
brez Avatar answered Oct 16 '22 11:10

brez