You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何并行执行界面中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__'保护,可将测量逻辑绑定到界面信号,放到入口块内执行:

  1. 调整主程序入口(比如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())
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 00:57:39