如何并行执行界面中SDR设备的set_center_freq指令?
我用PySide6开发了一套界面系统,主窗口代码在system.py。用户点击首页的“Measurements”按钮后,启动measurementsWin.py。在该窗口完成设置并点击“Start Measurement”按钮后,配置会下发到基于gnuradio库开发的SDR设备。
点击“Start Measurement”后,需要根据用户设置变更SDR的频率(通过set_center_freq指令),这个操作要同时给两台不同设备发指令。为了提升测量效率,我想并行执行这两个频率切换操作,但遇到了以下问题:
- 用Threading尝试时,代码没有真正并行,耗时没变化;
- 改用multiprocessing时,Windows的spawn方法要求多进程代码定义在
if __name__ == '__main__'块中,但频率切换只是界面流程里的一小部分,没法放在这个块里; - 在Linux用fork方法时,程序会占满RAM崩溃,而我必须在Windows环境下实现功能。
我试过把频率切换代码独立成模块(比如frequency_switch),但因为需要在if __name__ == '__main__'中定义所以没用;也试过用subprocess调用独立脚本,但主程序已经加载了SDR配置,子进程要重新加载,耗时反而更长。
频率切换核心代码:
def switch_source(usrp_source, freq, lo_offset): usrp_source.set_center_freq(uhd.tune_request(freq, rf_freq=freq - lo_offset, rf_freq_policy=uhd.tune_request.POLICY_MANUAL), 0) def switch_sink(usrp_sink, freq, lo_offset): usrp_sink.set_center_freq(uhd.tune_request(freq, rf_freq=freq - lo_offset, rf_freq_policy=uhd.tune_request.POLICY_MANUAL), 0)
SDR配置通过View类加载,启动测量后在循环中执行频率切换:
def sdrRunView(): tb = View() tb.start() measurement_start = True while done: # doing something. switch_source(self.uhd_usrp_source_0, freq, lo_offset) switch_sink(self.uhd_usrp_sink_0, freq, lo_offset) # doing something.
请问怎么实现这两个函数的并行执行?或者怎么调整程序入口安全点适配需求?
方案1:用concurrent.futures.ThreadPoolExecutor优化线程执行
之前线程未并行可能是因为没正确利用GIL释放机制——gnuradio的UHD驱动底层是C实现,调用set_center_freq时会释放Python的GIL,线程可以真正并行。用线程池复用线程资源,避免重复创建开销:
from concurrent.futures import ThreadPoolExecutor # 在循环外初始化线程池,复用线程 executor = ThreadPoolExecutor(max_workers=2) def sdrRunView(): tb = View() tb.start() measurement_start = True while done: # doing something. # 提交两个切换任务到线程池 future1 = executor.submit(switch_source, tb.uhd_usrp_source_0, freq, lo_offset) future2 = executor.submit(switch_sink, tb.uhd_usrp_sink_0, freq, lo_offset) # 等待两个任务完成 future1.result() future2.result() # doing something.
方案2:适配Windows多进程的入口结构
Windows的spawn机制要求主入口被if __name__ == '__main__'保护,可将测量逻辑绑定到界面信号,放到入口块内执行:
- 调整主程序入口(比如
system.py):
import sys from PySide6.QtWidgets import QApplication, MainWindow if __name__ == '__main__': app = QApplication(sys.argv) main_win = MainWindow() # 你的主窗口类 main_win.show() # 将测量启动逻辑绑定到界面信号 def start_measurement(): sdrRunView() main_win.start_measurement_btn.clicked.connect(start_measurement) sys.exit(app.exec())
- 修改
sdrRunView内部的多进程调用:
from multiprocessing import Process def sdrRunView(): tb = View() tb.start() measurement_start = True while done: # doing something. # 定义子进程执行的函数 def run_source_switch(): switch_source(tb.uhd_usrp_source_0, freq, lo_offset) def run_sink_switch(): switch_sink(tb.uhd_usrp_sink_0, freq, lo_offset) # 创建并启动进程 p1 = Process(target=run_source_switch) p2 = Process(target=run_sink_switch) p1.start() p2.start() # 等待进程完成 p1.join() p2.join() # doing something.
注意:如果UHD设备对象无法跨进程复用,此方案可能失效,优先考虑线程方案。
方案3:用PySide6的QThread适配Qt界面
针对Qt界面场景,用框架原生线程类避免线程与界面事件循环冲突:
from PySide6.QtCore import QThread, Signal # 定义频率切换线程类 class SourceSwitchThread(QThread): finished = Signal() def __init__(self, usrp_source, freq, lo_offset): super().__init__() self.usrp_source = usrp_source self.freq = freq self.lo_offset = lo_offset def run(self): switch_source(self.usrp_source, self.freq, self.lo_offset) self.finished.emit() class SinkSwitchThread(QThread): finished = Signal() def __init__(self, usrp_sink, freq, lo_offset): super().__init__() self.usrp_sink = usrp_sink self.freq = freq self.lo_offset = lo_offset def run(self): switch_sink(self.usrp_sink, self.freq, self.lo_offset) self.finished.emit() # 在测量循环中使用 def sdrRunView(): tb = View() tb.start() measurement_start = True while done: # doing something. # 创建线程实例 source_thread = SourceSwitchThread(tb.uhd_usrp_source_0, freq, lo_offset) sink_thread = SinkSwitchThread(tb.uhd_usrp_sink_0, freq, lo_offset) # 启动线程并等待完成 source_thread.start() sink_thread.start() source_thread.wait() sink_thread.wait() # doing something.
内容的提问来源于stack exchange,提问作者Oğuzhan Gedikli

