简体   繁体   English

优雅地退出无限循环线程

[英]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, eg 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. 有效地使用CPU。

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. 这满足第1,2和3点,但是忙碌的等待循环使用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. 我们现在不打击CPU,所以我们解决了1和4,但是当资源准备好或者我们被要求退出时,我们可以不必要地等待a_short_while

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. 这将优雅地满足1,2和4,但是现在如果资源不可用,我们不能要求线程退出。

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. 只要能够实现CPU使用率和响应性之间的权衡,最好的方法似乎是第二个,睡眠时间短。 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? 有没有一种方法可以满足所有这四个要求?

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

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). 在POSIX系统上,如果您使用的资源是文件或文件描述符(包括套接字),则可以使用select(2)poll(2)系统调用来执行此操作。 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. 如果你在Windows上, select()函数仍然适用于套接字,但适用于套接字,因此你应该安装使用WaitForMultipleObjects来等待资源句柄和事件句柄。 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);
}

You get a efficient solution if your obtain_ressource() function supports a timeout value: 如果您的obtain_ressource()函数支持超时值,您将获得有效的解决方案:

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. 这有效地将sleep()obtain_ressurce()调用结合起来。

Check out the manpage for nanosleep : 查看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. 如果nanosleep()函数因为被信号中断而返回,则该函数返回值-1并设置errno以指示中断。

In other words, you can interrupt sleeping threads by sending a signal (the sleep manpage says something similar). 换句话说,您可以通过发送信号来中断休眠线程( sleep说明了类似的内容)。 This means you can use your 2nd approach, and use an interrupt to immediately wake the thread if it's sleeping. 这意味着您可以使用第二种方法,并使用中断在线程休眠时立即唤醒线程。

Use the Gang of Four Observer Pattern: 使用Gang of Four观察者模式:

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

Callback, don't block. 回调,不要阻止。

Self-Pipe trick can be used here. 这里可以使用Self-Pipe技巧。 http://cr.yp.to/docs/selfpipe.html Assuming that you are reading the data from file descriptor. http://cr.yp.to/docs/selfpipe.html假设您正在从文件描述符中读取数据。

Create a pipe and select() for readability on the pipe input as well as on the resource you are interested. 创建一个管道并选择()以获取管道输入以及您感兴趣的资源的可读性。 Then when data comes on resource, the thread wakes up and does the processing. 然后,当数据进入资源时,线程会唤醒并执行处理。 Else it sleeps. 否则就会睡觉。 To terminate the thread send it a signal and in signal handler, write something on the pipe (I would say something which will never come from the resource you are interested in, something like NULL for illustrating the point). 要终止线程,发送一个信号并在信号处理程序中,在管道上写一些东西(我会说一些永远不会来自您感兴趣的资源的东西,类似于NULL来说明这一点)。 The select call returns and thread on reading the input knows that it got the poison pill and it is time to exit and calls pthread_exit(). select调用返回并且读取输入的线程知道它有毒丸,是时候退出并调用pthread_exit()。

EDIT: Better way will be just to see that the data came on the pipe and hence just exit rather than checking the value which came on that pipe. 编辑:更好的方法是只看到数据来自管道,因此只是退出而不是检查管道上的值。

The Win32 API uses more or less this approach: Win32 API使用或多或少的方法:

someThreadLoop( ... )
{
  MSG msg;
  int retVal;

  while( (retVal = ::GetMessage( &msg, TaskContext::winHandle_, 0, 0 )) > 0 )
  {
    ::TranslateMessage( &msg );
    ::DispatchMessage( &msg );
  }
}

GetMessage itself blocks until any type of message is received therefore not using any processing ( refer ). GetMessage本身会阻塞,直到收到任何类型的消息,因此不使用任何处理( 参考 )。 If a WM_QUIT is received, it returns false, exiting the thread function gracefully. 如果收到WM_QUIT,则返回false,正常退出线程函数。 This is a variant of the producer/consumer mentioned elsewhere. 这是其他地方提到的生产者/消费者的变体。

You can use any variant of a producer/consumer, and the pattern is often similar. 您可以使用生产者/消费者的任何变体,并且模式通常类似。 One could argue that one would want to split the responsibility concerning quitting and obtaining of a resource, but OTOH quitting could depend on obtaining a resource too (or could be regarded as one of the resources - but a special one). 有人可能会争辩说,人们会想要分担关于退出和获取资源的责任,但是OTOH退出也可能取决于获得资源(或者可以被视为资源之一 - 但是特殊资源)。 I would at least abstract the producer consumer pattern and have various implementations thereof. 我至少会抽象出生产者消费者模式,并有各种各样的实现。

Therefore: 因此:

AbstractConsumer: AbstractConsumer:

void AbstractConsumer::threadHandler()
{
  do
  {
    try
    {        
      process( dequeNextCommand() ); 
    }
    catch( const base_except& ex )
    {
      log( ex );
      if( ex.isCritical() ){ throw; }
      //else we don't want loop to exit...
    }
    catch( const std::exception& ex )
    {
      log( ex );
      throw; 
    }
  }
  while( !terminated() );
}

virtual void /*AbstractConsumer::*/process( std::unique_ptr<Command>&& command ) = 0;
//Note: 
// Either may or may not block until resource arrives, but typically blocks on
// a queue that is signalled as soon as a resource is available.
virtual std::unique_ptr<Command> /*AbstractConsumer::*/dequeNextCommand() = 0;
virtual bool /*AbstractConsumer::*/terminated() const = 0;

I usually encapsulate command to execute a function in the context of the consumer, but the pattern in the consumer is always the same. 我通常封装命令以在消费者的上下文中执行函数,但消费者中的模式始终是相同的。

Any (welln at least, most) approaches mentioned above will do the following: thread is created, then it's blocked wwiting for resource, then it's deleted. 上面提到的任何(最好的,最好的)方法将执行以下操作:创建线程,然后阻止wwiting for resource,然后将其删除。

If you're worried about efficiency, this is not a best approach when waiting for IO. 如果您担心效率,那么在等待IO时这不是最好的方法。 On Windows at least, you'll allocate around 1mb of memory in user mode, some in kernel for just one additional thread. 至少在Windows上,你将在用户模式下分配大约1mb的内存,在内核中只分配一个额外的线程。 What if you have many such resources? 如果你有很多这样的资源怎么办? Having many waiting threads will also increase context switches and slow down your program. 拥有许多等待线程也会增加上下文切换并降低程序速度。 What if resource takes longer to be available and many requests are made? 如果资源需要更长时间并且提出了许多请求,该怎么办? You may end up with tons of waiting threads. 你最终可能会有大量的等待线程。

Now, the solution to it (again, on Windows, but I'm sure there should be something similar on other OSes) is using threadpool (the one provided by Windows). 现在,它的解决方案(再次,在Windows上,但我确信在其他操作系统上应该有类似的东西)是使用线程池(Windows提供的)。 On Windows this will not only create limited amount of threads, it'll be able to detect when thread is waiting for IO and will stwal thread from there and reuse it for other operations while waitting. 在Windows上,这不仅会创建有限数量的线程,它还能够检测线程何时等待IO,并将从那里查找线程并在等待时将其重新用于其他操作。

See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686766(v=vs.85).aspx 请参阅http://msdn.microsoft.com/en-us/library/windows/desktop/ms686766(v=vs.85).aspx

Also, for more fine-grained control bit still having ability give up thread when waiting for IO, see IO completion ports (I think they'll anyway use threadpool inside): http://msdn.microsoft.com/en-us/library/windows/desktop/aa365198(v=vs.85).aspx 此外,对于更细粒度的控制位仍然具有在等待IO时放弃线程的能力,请参阅IO完成端口(我认为他们无论如何都会在内部使用线程池): http//msdn.microsoft.com/en-us/库/窗/桌面/ aa365198(v = vs.85)的.aspx

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM