在读取大文件时杀死std::thread

Kill std::thread while reading a big file

本文关键字:std thread 读取 文件      更新时间:2023-10-16

我有一个std::thread函数,它调用fopen将一个大文件加载到数组中:

void loadfile(char *fname, char *fbuffer, long fsize)
{
  FILE *fp = fopen(fname, "rb");
  fread(fbuffer, 1, fsize, fp);
  flose(fp);
}

这是由调用的

std::thread loader(loadfile, fname, fbuffer, fsize);
loader.detach();

在某个时刻,我的程序中有东西想要停止读取该文件,并要求另一个文件。问题是,当我删除fbuffer指针时,loader线程仍在运行,并且我得到了一个消除异常的竞赛条件。

我怎样才能弄死那根线?我的想法是检查fbuffer的存在,并可能将fread分成小块:

void loadfile(char *fname, char *fbuffer, long fsize)
{
  FILE *fp = fopen(fname, "rb");
  long ch = 0;
  while (ch += 256 < fsize)
  {
     if (fbuffer == NULL) return;
     fread(fbuffer + ch, 1, 256, fp);
  }
  fclose(fp);
}

这会减慢文件的读取速度吗?你有更好的主意吗?

您应该避免不惜一切代价杀死线程。这样做会导致邪恶的事情发生,比如资源被永久锁定。

必须为线程提供一个对标志的引用,该标志的值可以从其他地方设置,以告诉线程自愿退出。

您不能将缓冲区用于此目的;如果一个线程在另一个线程写入缓冲区时删除了缓冲区的内存,那么就会发生非常糟糕的事情。(内存损坏。)所以,传递一个对布尔标志的引用。

当然,为了让线程能够定期检查标志,它必须有小块的工作要做,所以将fread划分为小块是个好主意。

256字节可能有点太小了;肯定使用4k或更多,甚至64k。

通常不会杀死线程-这样做可能会导致资源泄漏、无法退出的关键部分以及程序状态不一致。

你的想法几乎是准确的,但你需要一种方式来通知线程完成。您可以使用线程和线程在每次读取后读取的其余代码之间共享的布尔值,一旦设置了布尔值,停止读取缓冲区,清理文件句柄并干净退出。

另一方面,在现代C++中,大多数时候都不赞成自己处理具有所属语义的指针的删除——除非你有充分的理由不这样做,否则我建议使用stl-fstream和字符串类。

您需要正确的线程同步。关于资源泄漏的评论和@Mike Nakis关于通过设置布尔值自愿退出线程的建议几乎是正确的(嗯,它们是正确的,但不完整)。你需要走得更远。

您不仅必须确保加载程序线程自行退出,还必须确保它在删除要写入的缓冲区之前已经退出。或者,至少,您必须确保它在您删除缓冲区后不会以任何方式接触该缓冲区。检查指针是否为null无效有两个原因。首先,它无论如何都不起作用,因为您正在查看原始指针的副本(您必须使用指针指针或引用)。其次,更重要的是,即使检查有效,if语句和fread之间也存在竞争条件。换句话说,无法保证在fread访问缓冲区时不会释放缓冲区(无论块有多小)。

至少,您需要两个布尔标志,但最好使用适当的同步原语(如条件变量)来通知主线程(这样您就不必旋转等待加载程序退出,但可以阻止)。

正确的操作方式是:

  1. 通知加载程序线程
  2. 等待加载程序线程向我发出信号(cond-var上的块)
  3. Loader线程接收通知,设置条件变量,之后再也不接触缓冲区,然后退出
  4. 恢复(删除缓冲区、分配新缓冲区等)

如果你不坚持分离加载程序线程,你可以在告诉它退出后简单地join它(所以你不需要第二个var)。