QT GUI按钮启停USRP录制:如何彻底终止含UHD子线程的任务
如何彻底终止UHD USRP录制线程及子线程?
问题分析
你当前的实现使用QThread::terminate()强制终止线程,但这种方式存在两个核心问题:
- 不安全且无效:
terminate()仅能终止Qt创建的线程,无法影响UHD库内部自行创建的子线程,导致录制流程无法彻底停止。 - 无协作退出逻辑:
run_usrp是阻塞式执行的函数,没有主动检查停止信号的逻辑,即使主线程发出终止命令,它也不会主动退出。
要实现类似CTRL-C的彻底终止效果,必须采用协作式停止+UHD原生终止接口的组合方案,而非强制终止线程。
解决方案
步骤1:添加线程安全的停止标志
在MainWindow类中添加原子布尔变量,用于跨线程传递停止信号(原子变量保证多线程访问的安全性):
// MainWindow.h #include <atomic> class MainWindow : public QMainWindow { Q_OBJECT // ... 其他已有成员 private: std::atomic<bool> m_stopRequested{false}; QThread* m_usrpThread = nullptr; // 保存线程指针,方便后续管理 };
步骤2:重构EXECUTE按钮逻辑
移除QThread::terminate()的连接,改为保存线程指针并绑定线程结束后的清理逻辑:
void MainWindow::on_button_EXECUTE_clicked() { if (ui->calculated_StartTime->text().isEmpty()) { QMessageBox::critical(this, "Error", "Hit the \"CALCULATE SCHEDULE\" button first above!!"); return; } ui->button_STOP->setVisible(true); ui->button_EXECUTE->setVisible(false); m_stopRequested.store(false); // 重置停止标志 m_usrpThread = QThread::create([this]{ execute_run_usrp(); }); // 线程结束后自动清理并恢复UI状态 connect(m_usrpThread, &QThread::finished, [this]{ m_usrpThread->deleteLater(); m_usrpThread = nullptr; ui->button_STOP->setVisible(false); ui->button_EXECUTE->setVisible(true); }); m_usrpThread->start(); }
步骤3:修改execute_run_usrp传递停止标志
将停止标志传递给run_usrp函数,让它能感知停止请求:
void MainWindow::execute_run_usrp() { float startFreq = ui->input_startFreq->text().toFloat(); float stopFreq = ui->input_stopFreq->text().toFloat(); float stepFreq = ui->input_stepFreq->text().toFloat(); int nRepeats = ui->input_numRepeats->text().toInt(); float ipp = ui->input_IPP->text().toFloat(); int sweepCadence = ui->calculated_sweepCadence->text().toInt(); int numSweeps = ui->input_numSweeps->text().toInt(); std::string schedule_run = ui->calculated_StartTime->text().toStdString(); // 传递停止标志引用给run_usrp run_usrp(startFreq, stopFreq, stepFreq, nRepeats, ipp, sweepCadence, numSweeps, schedule_run, m_stopRequested); }
步骤4:修改run_usrp添加停止检查与UHD终止逻辑
在run_usrp中定期检查停止标志,并调用UHD原生接口终止录制流程:
// 修改run_usrp的函数签名,新增停止参数 void run_usrp(float startFreq, float stopFreq, float stepFreq, int nRepeats, float ipp, int sweepCadence, int numSweeps, const std::string& schedule_run, std::atomic<bool>& stopRequested) { // 初始化UHD设备 uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(""); // ... 配置频率、采样率等参数 // 创建接收流对象 uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(...); // 启动连续流(示例) uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); rx_stream->issue_stream_cmd(stream_cmd); // 录制循环逻辑 std::vector<std::complex<float>> buff(1024); uhd::rx_metadata_t md; while (numSweeps-- > 0) { // 每次循环前检查停止标志 if (stopRequested.load(std::memory_order_acquire)) { // 发送停止流命令给UHD rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); break; } // 带超时的接收操作,避免永久阻塞 size_t num_rx = rx_stream->recv(&buff[0], buff.size(), md, 0.1); // 0.1秒超时 if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT || stopRequested.load()) { rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); break; } // ... 处理接收的数据 } // 销毁UHD设备对象,自动终止所有内部子线程并释放资源 usrp.reset(); }
步骤5:修改STOP按钮逻辑触发停止请求
void MainWindow::on_button_STOP_clicked() { m_stopRequested.store(true, std::memory_order_release); // 可选:请求线程中断,配合QThread::isInterruptionRequested()使用 if (m_usrpThread) { m_usrpThread->requestInterruption(); } }
方案说明
- 协作式停止:通过原子变量传递停止信号,让
run_usrp主动检查并退出,避免强制终止线程带来的资源泄漏问题。 - UHD原生终止:调用
issue_stream_cmd发送停止命令,直接通知UHD的流线程停止工作;销毁usrp对象会彻底释放所有UHD创建的子线程和硬件资源,达到与CTRL-C一致的终止效果。
内容的提问来源于stack exchange,提问作者NoRainDropsInTheSky
相关产品推荐
相关产品推荐

