事件触发时停止线程的实现问题及mutex抢占解决方案
咱们先捋清楚你当前代码的核心问题:你的子线程在每次迭代解锁后,立刻又进入下一次循环的lock()操作,主线程在sleep 1ms后尝试获取锁的时候,几乎每次都抢不过子线程——子线程解锁后马上就重新加锁了,主线程根本没机会拿到锁去修改stop变量,直到子线程跑完10次循环才释放锁,这时候修改stop已经完全没用了。
下面给你两个针对性的解决方案:
方案1:用std::condition_variable实现优雅的线程终止
我们可以用条件变量来让子线程在等待主线程信号的间隙释放锁,这样主线程能轻松拿到锁并发送停止信号。另外注意别用volatile,C++里线程间的内存可见性得靠同步原语(比如mutex、condition_variable)来保证,volatile根本不适合这个场景。
修改后的代码如下:
#include <thread> #include <mutex> #include <iostream> #include <chrono> #include <condition_variable> using namespace std; void function_(bool& stop, mutex& mtx, condition_variable& cv) { unique_lock<mutex> lock(mtx); for (int i = 0; i < 10; i++) { // 先检查是否要停止 if (stop) { return; } // 解锁去执行任务,别一直占着锁 lock.unlock(); //doSomething(); this_thread::sleep_for(chrono::milliseconds(1)); cout << "Iteration " << i << endl; // 重新加锁,准备下一次检查或等待 lock.lock(); // 等待主线程的信号,最多等1ms(也可以无限等,看你的需求) cv.wait_for(lock, chrono::milliseconds(1)); } } int main() { bool stop = false; mutex mtx; condition_variable cv; thread functionThread(function_, ref(stop), ref(mtx), ref(cv)); this_thread::sleep_for(chrono::milliseconds(1)); { lock_guard<mutex> lock(mtx); stop = true; } // 发送信号唤醒子线程,让它立刻检查停止条件 cv.notify_one(); cout << "Changed boolean variable value" << endl; functionThread.join(); system("pause"); return 0; }
关键改动说明:
- 用
std::unique_lock代替直接调用lock()/unlock(),它能更灵活地控制锁的释放和重获取 - 子线程在执行任务(sleep或doSomething)前先解锁,避免一直占用锁,给主线程留足修改
stop的机会 - 用
condition_variable::wait_for让子线程在等待时自动释放锁,主线程可以随时发送信号唤醒它检查停止条件 - 主线程修改
stop后,调用notify_one()唤醒子线程,确保它立刻响应停止信号
这样运行后,子线程只会执行1-2次迭代就会停止,完全符合你的预期。
方案2:处理doSomething()耗时较长的情况
如果doSomething()执行时间很长,而且你不能修改它的内部代码,那没有安全的跨平台方法可以强制终止线程——C++标准根本不支持强制终止线程,因为这会导致线程持有的资源(比如mutex、内存)无法正确释放,引发各种未定义行为(程序崩溃、资源泄漏等)。
那我们能做的最优选择是:
- 在子线程调用
doSomething()前先检查stop标志,如果已经要停止就直接返回;如果已经进入doSomething()执行,那只能等它执行完再检查停止条件。修改后的子线程代码大概是这样:
void function_(bool& stop, mutex& mtx, condition_variable& cv) { unique_lock<mutex> lock(mtx); for (int i = 0; i < 10; i++) { if (stop) { return; } lock.unlock(); // 调用耗时函数前再检查一次,避免白跑 if (stop) { lock.lock(); return; } doSomething(); // 耗时很长的函数 cout << "Iteration " << i << endl; lock.lock(); cv.wait_for(lock, chrono::milliseconds(1)); } }
这样如果主线程在子线程调用doSomething()前发送了停止信号,子线程就不会进入这个耗时函数;但如果已经进入了,那只能等它执行完。
如果你真的非要强制终止(完全不考虑安全风险),可以用平台相关的API,比如Windows下的TerminateThread,Linux下的pthread_cancel,但这些方法都是极其不安全的,大概率会搞崩你的程序,非常不推荐使用。
内容的提问来源于stack exchange,提问作者Aral

