干净取消在输入/输出调用中被阻止的 std::线程



我有一个可能在文件描述符输入/输出调用中被阻止的std::thread,如何干净地取消它?

请考虑以下示例:

#include <unistd.h>
#include <thread>
void thread_routine(int fd)
{
char buf;
read(fd, &buf, 1);
}
int main()
{
int pipefd[2];
pipe(pipefd);
std::thread thread(&thread_routine, pipefd[0]);
thread.join();
close(pipefd[0]);
close(pipefd[1]);
}

join()之前我可以做些什么来确保它不会永远锁定?(管道只是获取文件描述符的快速示例方法,我有一个更奇特的场景,但我正在尝试获得一个通用答案。

使用 pthreads,我可以调用pthread_cancel(),因为read()write()是取消点,但没有C++方法可以取消std::thread(我可以获取thread::native_handle()并将其传递给pthread_cancel(),但我想要一种更干净的方法)。

请注意:

  • 我无法在非阻塞模式下设置文件描述符
  • 我不能在文件描述符上使用 select()

使用std::future,您可以在有限的时间间隔内阻塞,并最终决定停止等待并分离线程。泄漏正在运行的线程是否要退出并不重要。如果不退出,则阻塞调用将自然返回并销毁线程,或者无论如何都会在进程的剩余生存期内被阻塞。

在下面的示例中,MAX_WAIT_TIME用作分离和退出的条件。

#include <chrono>
#include <future>
#include <thread>
using namespace std::literals;
const auto BLOCK_DURATION = 100ms;
const auto MAX_WAIT_TIME = 3s;
int main() {
// Some blocking call.
auto blocking_call = [] { std::this_thread::sleep_for(1min); };
// Wrap the callable in a packaged task and get future.
std::packaged_task<void()> task{blocking_call};
auto future = task.get_future();
auto start_time = std::chrono::steady_clock::now();
std::thread t{std::move(task)};
// Continually check if task is finished, i.e., if blocking call has returned.
auto status = future.wait_for(0s);
while (status != std::future_status::ready) {
status = future.wait_for(BLOCK_DURATION);
// Room for condition to stop blocking, e.g., check some signaling etc.
auto duration = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time);
if (duration > MAX_WAIT_TIME) {
break;
}
}
if (status == std::future_status::ready) {
future.get(); // Optionally get return value.
t.join();
} else {
t.detach();
}
/* And we're on our way... */
}

当呼叫返回时,std::future::wait_for将立即返回,即使完整的等待时间尚未过去。

如果时间是通话可能阻塞的时间限制,那么甚至还有整洁的std::future::wait_until例如

auto start_time = std::chrono::steady_clock::now();
std::thread t{std::move(task)};
auto status = future.wait_until(start_time + MAX_WAIT_TIME);
if (status == std::future_status::ready) {
t.join();
} else {
t.detach();
}

我相信答案取决于系统。有一些方法可以做到这一点,它在不同的操作系统中的工作方式可能不同。

  1. 关闭文件描述符。 比操作不会阻塞,但总是会以错误告终。 同样被阻止的操作最终会出现错误。

  2. 另一种方法是发送一个信号,该信号将由该线程处理并中断任何系统操作。

  3. (
  4. 如果可以使用 select)在 read() 调用前面加上 select() 并尝试方法 1。

  5. (如果可以使用选择)添加另一个信令套接字并选择两个。如果信号插座可以读取,则退出。

  6. (如果您可以使用选择)将所有逻辑粘贴到一个线程中可能是有意义的。例如,与GPS/Sockets/Stdio一起工作的NTPD是一个单线程应用程序,只有一个选择!

我认为方法 4 是最好的。但是如果你不能使用选择,你可以尝试2和1

不要使用pthread_cancel。这真是不好的做法。

最新更新