在走这段代码的时候报错了, 记录一下我的调试过程, 感觉有个思路来走就挺好的.
1, 报错与解决
文件名字: ClassifierTest.py
- import torch
- import torchvision
- import torchvision.transforms as transforms
- from torchTest import imgShow
- transform = transforms.Compose([transforms.ToTensor(),
- transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
- trainset = torchvision.datasets.CIFAR10(root='Resources/CIFAR10', # 存放路径 #!!/Resources/CIFAR10 是绝对路径, C:\Resources\CIFAR10
- train=True, download=True, # 是否下载训练集
- transform=transform) # 图片转换
- trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)
- testset = torchvision.datasets.CIFAR10(root='Resources/CIFAR10', train=False, download=True, transform=transform)
- testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)
- classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
- dataIter = iter(trainloader)
- images, labels = dataIter.next()
- imgShow.imshow(torchvision.utils.make_grid(images))
- print(' '.join(classes[labels[j]] for j in range(4)))
报错
- 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.
关于这个报错, 涉及线程问题, 改 num_workers=0, 当然就么事没有, 然而, 作为一个优秀的程序员, 能止步于此吗, 不行的.
我百度了一下报错情况, 找到这样的解决方案, 是可行:
- def main():
- transform = transforms.Compose([transforms.ToTensor(),
- transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
- trainset = torchvision.datasets.CIFAR10(root='Resources/CIFAR10', # 存放路径, 注:/Resources/CIFAR10 是绝对路径, C:\Resources\CIFAR10
- train=True, download=True, # 是否下载训练集
- transform=transform) # 图片转换
- trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)
- testset = torchvision.datasets.CIFAR10(root='Resources/CIFAR10', train=False, download=True, transform=transform)
- testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)
- classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
- dataIter = iter(trainloader)
- images, labels = dataIter.next()
- imgShow.imshow(torchvision.utils.make_grid(images))
- print(' '.join(classes[labels[j]] for j in range(4)))
- if __name__=='__main__': #不加这句就会报错
- main()
2, 为什么是 main?
整段放在 main 里面, 就安全了 -- 为什么呢?
对于 python 编程我还是萌新, 实在想不明白加个__name__=='__main__'判断有什么魅力.
关于__name__属性: 作为启动脚本,它模块的__name__都是__main__。 此句主要作用在于有时候 import,不想运行引用模块中某些语句的时候,以启动模块的名字作为区别。 |
报错的位置在这里:
C:\Users\13723\AppData\Local\Programs\Python\Python39\Lib\multiprocessing\spawn.py
- def _check_not_importing_main():
- if getattr(process.current_process(), '_inheriting', False):
- raise RuntimeError('''
- An attempt has been made to start a new process before the
- current process has finished its bootstrapping phase...''')
getattr(实例, 属性名字, 默认值) 如果有属性,取 True,否则取默认值,没有默认值则取 False。 |
_inheriting, 查找当前程序的可继承性? 没用过, 笔者不知道呢.
看不懂 (下文有解), 只能从方法名字入手, 它走这一段为了什么 --
检查是不是源自__main__模块, 即程序不让由执行脚本 import 的模块走这一段.
我跑 ClassifierTest.py(进程 pid1), 它在走到
dataIter = iter(trainloader)
里面, 由其他模块, 再导入了一次 ClassifierTest.py(此时是进程 pid2)
而当增加判断 __name__=='__main__', 就避免模块陷入执行的死循环.
3, 为什么多一个进程?
3.1 现象
为什么会多一个进程, num_workers=2, 此句是一个进程两个线程 worker, 还是两个进程 worker 呢?
我很奇怪, 为什么不是开线程, 而是开进程这么个重量级东西.
虽然叫做 process, 但它应该只干一个事情 -- 毕竟进程的重量级要大于线程.
3.2 线程与进程
这个时候就很纠结线程和进程的区别了,
(参考: https://www.zhihu.com/question/25532384)
线程是 CPU 执行的时间段颗粒,
进程保存上下文, CPU 切进进程里面读取上下文 (寄存器, 指令内容之类).
这样看来, 如果进程是仓库, 线程就是仓库里面的机器人, 等待 CPU 来灵魂激活. 但是在一个仓库里面工作, 必然比在多个仓库里面工作要省事.
所以为什么要开多进程呢?
一个莫名的灵感, 让我查了一下 fork(),
(参考: https://www.cnblogs.com/liyuan989/p/4279210.html)
因为进程、线程是 windows 系统的概念,unix 中只有进程的说法。 在 windows 当中,进程是资源管理的最小单位,而线程是程序执行的最小单位。 fork 创建的一个子进程几乎但不完全与父进程相同。 子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份拷贝,包括文本、数据和 bss 段、堆以及用户栈等。 子进程还获得与父进程任何打开文件描述符相同的拷贝,这就意味着子进程可以读写父进程中任何打开的文件,父进程和子进程之间最大的区别在于它们有着不同的 PID。 |
fork 意为分支, 分支与父进程几乎一样的子进程. 子进程区别于父进程, 两者有不同的 pid, 但二者的引用均指向相同的地址.
3.3 Python 中, worker 是进程
为什么会再读一次 ClassifierTest.py, 从堆栈看, 是这里:
(注, 以下截图可能取自不同次调试, 所以父 pid 会不同)
走了 exec(code, run_globals) 导致再此导入 ClassifierTest.py .
再往前走 frame not available, 也即 IDE 只能看到 spawn_main 函数.
(spawn 应该就是孵化了, 孵化进程的, 还挺有蛇下蛋的感觉)
更之前的调用情况没有了, 可以猜是不是新进程直接调用 spawn_main 了, 那就找 spawn_main 引用.
(可能 pyCharm 我还没 get 灵魂用法, spawn_main 引用我是用 notepad++ 查找全局的)
Python39\Lib\multiprocessing\popen_spawn_win32.py
前后呼应:
在查看堆栈的过程中, 恰巧看到了_inheriting 的赋值:
堆栈可以看到对_inheriting 赋值, 此时就很明了表示是否子进程, 此处赋值 True.
再者, inheriting 是 ing 结尾, 表示进行时状态; 如果是表示继承性, 应该叫 inherited, 如此看来这个编程就很细心, 自己写程序的时候也得注意.
3.4 num_workers=2 的结果
前文设置 num_workers = 2, 此时就是父进程带着两个子进程,
__name__=='__main__' 的处理, 阻止了子进程由于调用 ClassifierTest.py 而再生子子进程的子孙无穷尽也.
主线程 12012 它有两个 worker, 分别是 15480 和 7036 .
15480 和 7036 带着自己的 Queue,dataloader.py 完成了这个配置.
- dataIter = iter(trainloader)
- images, labels = dataIter.next()
当执行 next(), 程序会读取象 dataIter 当中的 _data_queue , 这个数据由两个子进程各自传入.
data = self._data_queue.get(timeout=timeout)
具体实现看这个类:
- C:\Users\13723\PycharmProjects\pythonProject\venv\Lib\site-packages\torch\utils\data\dataloader.py
- class _MultiProcessingDataLoaderIter(_BaseDataLoaderIter):
- pass
4, 结语
由一个小小的报错, 能 "查漏补缺" 知识漏洞就挺好的, 锻炼思维也挺好的. 共勉.
来源: http://www.bubuko.com/infodetail-3803401.html