【问题标题】:C++ thread can't abort after being joinedC++线程在加入后不能中止
【发布时间】:2021-03-05 03:06:28
【问题描述】:

所以我的程序中有 2 个线程在运行,我最终希望它们在调用此函数时终止:

void AutoClicker::TerminateThreads()
{
    programrunning = false;
    clicker.join();
    hotkeyLoop.join();
    terminateThread = true;
}

然后调用线程应该运行这段代码:

while (1)
{
    while (programrunning)
    {
        mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
        mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
        std::this_thread::sleep_for(std::chrono::milliseconds(delay));
    }

    if (terminateThread) {
        std::terminate();
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
}

但最后一个if 语句似乎没有被调用。它位于我试图终止的线程内,所以它应该可以正常工作吗?我对线程很陌生,所以我真的不知道该怎么做,但这是我在网上找到的:

“当std::thread 对象被销毁时,它不允许被joinable(),即在它被销毁之前必须发生两件事之一:

它被分离了,线程已经进入野外,几乎无法控制它是否完成。

话题已经join()ed。"

【问题讨论】:

  • 如果这是真正的代码,您无法保证线程会看到对programrunning 的更改,因为没有任何类型的同步。更糟糕的是,一个线程可能会访问programrunning,而另一个线程正在修改它,这可能会被您编码的任何线程标准所禁止。 (C++ 标准禁止这样做,除非 programrunningstd::atomic 或其他我们无法分辨的东西。)
  • 对不起,我对线程很陌生,但是我将如何同步程序运行,因为我还在另一个函数中更改它,如果按下热键,它会显示,如果是,则切换程序运行变量并且工作正常
  • 假设您使用的是 C++ 标准线程,您必须遵循它们的规则。 C++ 标准(在第 1.10 节中)说具有数据竞争的程序表现出未定义的行为。您的程序存在数据竞争,因为一个线程可以在另一个线程访问它的同时修改programrunning。您根本不允许这样做。 “本国际标准对包含未定义行为的程序的行为没有任何要求。......任何此类数据竞争都会导致未定义行为。”你不能让线程互相践踏,必须协调它们。
  • (如果programrunningstd::atomic,则忽略上述内容,因为它的内部同步不允许两个操作同时发生,因此不会是数据竞争。)
  • 从代码中不清楚,但如果 while 循环作为第一个代码 sn-p 中加入的线程之一的一部分运行,则 while 中的代码循环不会看到对terminateThread 的更改。对join() 的调用都会阻塞,直到相应的线程结束,所以分配来得太晚了。

标签: c++ multithreading visual-studio terminate


【解决方案1】:

从两件事开始:

  • 请勿致电std::terminate()。这不是拆除程序的好方法,并且可能会使打开的文件和其他操作系统资源挂起。此外,它适用于您的整个程序,而不仅仅是您调用它的线程。
  • 如果您希望 std::thread 终止,则只需从它返回或让它到达线程函数的末尾。

除此之外,这是一个小程序,展示了我将如何执行您所描述的操作:

#include <iostream>
#include <atomic>
#include <chrono>
#include <thread>

void threadfunc(std::atomic<bool>& running)
{
    while (running) {
        std::cout << "beep\n";
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

void waitThread(std::atomic<bool>& running)
{
    std::this_thread::sleep_for(std::chrono::seconds(10));
    running = false;
}

int main()
{
    std::atomic<bool> running = true;

    std::thread t1(threadfunc, std::ref(running));
    std::thread t2(waitThread, std::ref(running));
    t1.join();
    t2.join();
}

希望您可以在这里收集一些信息以使您的程序正常运行。

编辑: 从 OP 对另一个答案的评论中澄清一些内容:

类似:std::thread t1(threadfunc, std::ref(running)); 创建一个执行所提供函数的活动线程。一旦该函数停止(返回),线程就会处于僵尸状态,它不会执行但仍处于活动状态并持有资源。

https://en.cppreference.com/w/cpp/thread/thread/joinable

已完成执行代码但尚未加入的线程 仍然被认为是一个活动的执行线程,因此是 可加入。

为了将线程从僵尸状态中拉出来并清理它的资源,你必须在它上面调用join()。请注意,您可以在该线程执行之前或之后在该线程上调用join() - 如果您之前加入,您会阻塞直到加入的线程完成,如果您之后加入,则清理延迟线程并立即继续。

或者,您可以在线程上调用detach()

https://en.cppreference.com/w/cpp/thread/thread/detach

将执行线程与线程对象分开,允许 执行以独立继续。任何分配的资源将 线程退出后释放。

但是一旦你分离了一个线程,你就不能再加入它,并且在仍然有活动但分离的线程运行时让你的主线程退出会以一种不太好的方式杀死分离的线程。因此,只有在您确定分离的线程将在主线程之前完成时才这样做。

【讨论】:

  • 我会使用 cv/mutex/bool 并等待它们允许响应关闭,而不是等待睡眠周期
【解决方案2】:

你并没有真正拥有这里的所有代码,但我猜点击器线程中的方法可能看起来像

void clickerThreadFunction()
{
  while(programrunning)
  {
    // do stuff
  }

  if (terminateThread)
  {
    std::terminate();
  }
}

如果是这样,那就看代码

programrunning = false;
clicker.join();

第一行将导致点击器线程中的while 循环退出。 clicker 方法的其余部分将运行,包括 if (terminateThread) 检查,该检查将失败,因为您尚未将 terminateThread 设置为 true。只有这样,一旦我们到达clickerThreadFunction 的底部,clicker.join() 才会返回,因为std::thread::join 等待线程完成然后返回控制权。

无论如何,我不清楚您要在这里做什么。 std::terminate 的目的是立即使程序崩溃。如果您希望发生这种情况,则不清楚您为什么要打扰加入线程,因为无论如何您都不会到达那部分。

【讨论】:

  • 抱歉没有添加足够的信息我已经更新了我的帖子以显示整个线程
  • 好的,看到给定的代码,问题就是我上面描述的:循环无法终止,所以对clicker.join()的调用将永远不会返回。
  • (我猜“问题”是把它拉长了,因为其余的代码也没有意义。)
【解决方案3】:

你知道join 做什么吗? join 等待线程完成。

您的 TerminateThreads 函数将 programrunning 设置为 false,然后等待线程完成,然后将 terminateThread 设置为 true。

线程一直运行直到 terminateThread 变为 true,因此当 TerminateThreads 函数被调用时,它永远不会到达将 terminateThread 设置为 true 的部分,因为它正在等待线程完成。

【讨论】:

  • 哦,我明白了,但是我应该如何终止线程,因为另一个帖子说线程应该在终止之前加入?
  • @daniëldegroot 正在谈论终止(销毁)主线程中的线程对象。不终止实际线程。您可以终止线程,然后主线程加入它,然后主线程可以销毁线程对象。或者,主线程可以分离线程对象,然后在线程还在运行的时候销毁线程对象。
  • @daniëldegroot,说清楚一点; std::thread 不是线程。线程是操作系统中执行程序代码的对象。 std::thread 是程序中的一个对象,它充当操作系统对象的“句柄”。而且,std::thread 对象和操作系统对象的生命没有严格联系。任何一个都可能在另一个之前结束。当一个线程的生命结束时,我们说它被“终止”了。当你的程序中一个对象的生命结束时,我们说它被“销毁”了。
  • @Eljay 实际上你必须在销毁 std::thread 对象之前加入或分离,否则你的程序会崩溃。
  • @Eljay 这是强制的,检查文档
猜你喜欢
  • 1970-01-01
  • 2010-10-24
  • 1970-01-01
  • 1970-01-01
  • 2021-08-21
  • 1970-01-01
  • 2010-11-10
  • 1970-01-01
  • 2013-12-07
相关资源
最近更新 更多