data = self._data_queue.get(timeout=timeout)RuntimeError: DataLoader worker (pid(s) 8528, 8488) exit

本文聚焦Windows环境下运行Yolov5代码时,多线程取出数据报错问题。分析指出报错原因是Windows与Linux系统差异,Windows缺乏fork()系统调用,使用spawn命令有额外限制,忽略主模块安全导入要求导致报错。给出解决方法,在主函数第一行添加代码区分主进程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

报错输出

我在Windows环境下,运行Yolov5的代码时,代码中多线程取出数据出现了以下的错误:

RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.
Traceback (most recent call last):
  File "D:\Software\Anaconda\envs\pytorch\lib\site-packages\torch\utils\data\dataloader.py", line 761, in _try_get_data
    data = self._data_queue.get(timeout=timeout)
  File "D:\Software\Anaconda\envs\pytorch\lib\multiprocessing\queues.py", line 105, in get
    raise Empty
_queue.Empty

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:/WorkSpace/Codes/Pycharm/2020/NPyTorch/Minibatchtraing.py", line 24, in <module>
    for step, (batch_x, batch_y) in enumerate(loader):
  File "D:\Software\Anaconda\envs\pytorch\lib\site-packages\torch\utils\data\dataloader.py", line 345, in __next__
    data = self._next_data()
  File "D:\Software\Anaconda\envs\pytorch\lib\site-packages\torch\utils\data\dataloader.py", line 841, in _next_data
    idx, data = self._get_data()
  File "D:\Software\Anaconda\envs\pytorch\lib\site-packages\torch\utils\data\dataloader.py", line 808, in _get_data
    success, data = self._try_get_data()
  File "D:\Software\Anaconda\envs\pytorch\lib\site-packages\torch\utils\data\dataloader.py", line 774, in _try_get_data
    raise RuntimeError('DataLoader worker (pid(s) {}) exited unexpectedly'.format(pids_str))
RuntimeError: DataLoader worker (pid(s) 8528, 8488) exited unexpectedly

可以发现,上面的DataLoader有两个PID 8528, 8488,表示其有两个进程,而两个进程报错,则说明多进程的设置有问题。

出错原因

上述错误的提示含义为:您试图在当前进程完成引导阶段之前启动新进程。这可能意味着您没有使用fork来启动子进程,并且忘记了在主模块中使用适当的习语。还有一种情况是,如果您的程序不会被冻结为生成Windows可执行文件,则可以省略 “freeze_support()” 行。

其实真正原因的出现还是Windows操作系统与Linux操作系统的差异。

在Linux操作系统中,其提供了一个fork()系统调用,用于创建子进程,有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新 任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。

但是,Windows中是并没有fork()系统调用的,是使用的 spawn 命令,那该怎么办呢?

multiprocessing模块本来就是跨平台版本的多进程模块,其设计的时候就考虑到了Linux和Windows操作系统的差异,但因为Windows缺乏linix那种fork, 所以它会有一些额外的限制:

  • 不管是绑定还是未绑定的方法, 都不要直接作为参数传给Process初始化的target, 相反应该要用普通的函数代替
  • 子进程在访问全局变量时, 可能会与父进程的值不同 ( 模块级别的常量没这问题 )
  • 开启新Python解析器或者创建新process时, 确定主模块能够安全的导入.

这也就解释了为什么Windows下使用多进程模块 multiprocessing 必须要有主函数存在了,因为 spawn命令需要区分主进程,上述的报错其实就是忽略了第三点的要求。

解决方法

要解决上面的错误也很简单,在主函数 if __name__ == '__main__': 的第一行加上 multiprocessing.freeze_support() ,使得主进程能够得到区分,这里我是运行Yolov5的代码出错的,我就用其主函数来演示,其修改如下:

import multiprocessing


"""
其他代码
"""

if __name__ == "__main__":
    multiprocessing.freeze_support()
    opt = parse_opt()
    main(opt)
<think>好的,我现在需要逐行解释用户提供的Python类`DBUpdater`的代码。首先,我应该先整体看一下这段代码的结构,了解它的大致功能。看起来这是一个用于数据库更新的类,可能涉及到Oracle数据库的连接,因为它使用了`oracledb`库,并且有初始化客户端的部分。 接下来,用户给出的代码分为几个部分:类的初始化方法`__init__`,私有方法`_load_config`和`_log`。我需要逐一分析每个方法的作用和代码行。 首先看`class DBUpdater:`,这定义了一个名为DBUpdater的类。然后`def __init__(self, log_queue, progress_queue):`是类的构造函数,接收两个参数:log_queue和progress_queue。这两个参数可能是用于日志记录和进度更新的队列对象,用于多线程或进程间通信。 构造函数内部,`self.log_queue = log_queue`和`self.progress_queue = progress_queue`将传入的队列保存为实例变量,这样其他方法可以使用它们来传递日志消息和进度信息。接着调用`self._load_config()`,这是一个私有方法,用于加载配置,比如数据库连接信息。 接下来是`_load_config`方法。首先,`config = configparser.ConfigParser()`创建了一个ConfigParser对象,用来读取配置文件。不过,用户提供的代码中,接下来的几行似乎有问题,比如`self.dsn = dsn`,这里的dsn变量并没有定义,可能是用户代码中的错误,或者示例代码不完整。正确的做法应该是从配置文件中读取这些值,比如使用`config.get()`方法获取配置项。不过当前代码中没有这样的步骤,看起来这部分可能被简化或存在笔误。 然后,`oracledb.init_oracle_client(lib_dir=self.lib_dir)`这一行初始化了Oracle客户端库,指定了库所在的目录。这通常是因为Oracle的客户端库需要指定路径才能正确加载,尤其是在非标准安装路径下。 最后是`_log`方法,它接受一个消息参数,并将其放入log_queue中,这样其他进程或线程可以获取这些日志信息。 现在需要检查是否存在潜在的问题。例如,在`_load_config`方法中,直接给dsn、username、password等赋值,而没有从配置文件中读取,这可能导致错误,因为这些变量未定义。正确的做法应该是使用ConfigParser来读取配置文件中的相应部分,比如: ```python config.read('config.ini') self.dsn = config.get('database', 'dsn') self.username = config.get('database', 'username') # 其他类似 ``` 但用户提供的代码中没有这些步骤,可能用户只是写了一个示例,或者遗漏了这部分代码。在解释时需要指出这一点,说明当前代码可能存在的问题或需要补充的地方。 另外,需要解释oracledb库的用途,它是用于连接Oracle数据库的Python库,而init_oracle_client的作用是初始化客户端环境,特别是当需要指定lib_dir时,比如使用Instant Client的情况。 在逐行解释时,要确保每个变量和方法的用途都清楚,同时指出代码中可能存在的错误或需要完善的部分,比如配置加载部分的不完整。此外,提到队列的使用,说明这个类可能是在多线程或多进程环境中使用,用于异步传递日志和进度信息。 总结来说,我需要逐步解释每一行代码的作用,指出潜在的问题,并说明各个部分如何协同工作来实现数据库更新的功能。</think>让我们逐行分析这个`DBUpdater`类的代码实现: ```python class DBUpdater: # 类构造函数,接收日志队列和进度队列两个参数 def __init__(self, log_queue, progress_queue): self.log_queue = log_queue # 初始化日志消息队列 self.progress_queue = progress_queue # 初始化进度更新队列 self._load_config() # 调用私有方法加载配置 # 私有方法:加载数据库配置 def _load_config(self): config = configparser.ConfigParser() # 创建配置解析器 # 以下四行应读取配置文件,当前写法存在问题(变量未定义): self.dsn = dsn # 数据库服务名(应来自配置文件) self.username = username # 数据库用户名(应来自配置文件) self.password = password # 数据库密码(应来自配置文件) self.lib_dir = lib_dir # Oracle客户端库路径(应来自配置文件) # 初始化Oracle客户端环境 oracledb.init_oracle_client(lib_dir=self.lib_dir) # 日志记录方法 def _log(self, message): self.log_queue.put(message) # 将消息放入日志队列 ``` 代码结构解释表格: | 代码行 | 功能说明 | 当前存在问题 | |---------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------| | `class DBUpdater:` | 定义数据库更新器类 | - | | `def __init__(...):` | 构造函数接收两个队列参数 | - | | `self.log_queue` | 存储用于传递日志消息的队列 | - | | `self.progress_queue` | 存储用于传递进度信息的队列 | - | | `self._load_config()` | 调用配置加载方法 | - | | `config = configparser...` | 创建配置解析器实例 | - | | `self.dsn = dsn` | 应配置数据库连接标识符 | 变量未定义,应改为从配置文件读取(如`config.get('DB','dsn')`) | | `oracledb.init_oracle_client` | 初始化Oracle客户端库 | 需确保lib_dir路径有效,否则会报错 | | `def _log(...):` | 私有方法实现日志记录 | - | 需要改进的建议: 1. 配置加载部分应补充配置文件读取逻辑: ```python config.read('config.ini') self.dsn = config.get('Database', 'dsn') self.username = config.get('Credentials', 'username') self.password = config.get('Credentials', 'password') self.lib_dir = config.get('Paths', 'oracle_lib') ``` 2. 建议增加异常处理: ```python try: oracledb.init_oracle_client(lib_dir=self.lib_dir) except Exception as e: self._log(f"Oracle客户端初始化失败: {str(e)}") raise ``` 这个类的设计特点: - 采用队列实现异步通信,适用于多线程/多进程环境 - 封装数据库配置加载过程,符合单一职责原则 - 使用`oracledb`库实现Oracle数据库操作(需安装该第三方库) 数学表达式示例(根据系统指令): - 队列容量计算公式:$$ Q_{size} = \frac{\log_2(n)}{k} \quad (k > 0) $$ - 时间复杂度评估:$O(n \log n)$ 请注意当前代码中的配置加载部分存在逻辑缺失,建议按上述改进建议补充完整的配置文件读取逻辑以保证功能正常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值