Process模块创建进程大致过程

本文深入剖析Python的`multiprocessing`模块中`Process`类的创建和启动过程,包括`__init__`、`start`方法以及`Popen`的使用。在Windows平台上,详细解析了`popen_spawn_win32.Popen`类的实现,涉及子进程的创建、管道通信和信息传递等关键步骤。
摘要由CSDN通过智能技术生成

创建进程大致过程

Process BaseProcess BaseContext SpawnContext SpawnProcess .popen_spawn_win32.Popen _init self start _Popen _Popen _Popen _Popen Popen self Popen Popen Popen Process BaseProcess BaseContext SpawnContext SpawnProcess .popen_spawn_win32.Popen

multiprocessing.Process()

Process类

# Process类源码
class Process(process.BaseProcess):
    _start_method = None
    @staticmethod
    def _Popen(process_obj):
        return _default_context.get_context().Process._Popen(process_obj)

BaseProcess类

init方法
def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
             *, daemon=None):
    assert group is None, 'group argument must be None for now'
    count = next(_process_counter)  # 获取已有进程数量
    self._identity = _current_process._identity + (count,)  #进程号为父进程序号+序号
    self._config = _current_process._config.copy()  # 复制父进程的config信息,很明显此处是深拷贝
    self._parent_pid = os.getpid() # 设置父进程id
    self._parent_name = _current_process.name # 设置父进程名称
    self._popen = None  # 设置_popen
    self._closed = False # 设置_closed
    self._target = target # 设置 _target
    self._args = tuple(args) # 设置 _args 参数
    self._kwargs = dict(kwargs) # 设置 _kwargs 参数
    self._name = name or type(self).__name__ + '-' + \
                 ':'.join(str(i) for i in self._identity) # 如果没有名字,初始化名字
    if daemon is not None:
        self.daemon = daemon  # 设置当前进程是否需要守护
    _dangling.add(self) # 将进程放进一个集合
start方法
def start(self):
    # 检查进程是或执行过,_close属性是否为True
    self._check_closed()  
    # 一个进程不能执行两次
    assert self._popen is None
    # 进程对象 只能 由创建对象的进程 去就绪	
    assert self._parent_pid == os.getpid()
    # 守护进程不能拥有子进程   
    assert not _current_process._config.get('daemon')
    # 检查哪个进程已经结束了???目前不知道干啥的     
    _cleanup() 
    #此处的self._Popen(self) 不同平台返回的对象不一样
    # win 平台 .popen_spawn_win32.Popen
    # mac 平台 .popen_spawn_posix.Popen
    # 其他 平台 .popen_fork.Popen
    # import multiprocessing 章节,描述了一下具体原因
    self._popen = self._Popen(self) 
   
    self._sentinel = self._popen.sentinel
    # Avoid a refcycle if the target function holds an indirect
    # reference to the process object (see bpo-30775)
    del self._target, self._args, self._kwargs
    _children.add(self)

import multiprocessing

分析进程之前我们要看一下导入进程模块之后发生了什么,以下是multiprocessing模块中的context.py模块中部分代码

if sys.platform != 'win32':

    class ForkProcess(process.BaseProcess):
        _start_method = 'fork'
        @staticmethod
        def _Popen(process_obj):
            from .popen_fork import Popen
            return Popen(process_obj)

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_posix import Popen
            return Popen(process_obj)

    class ForkServerProcess(process.BaseProcess):
        _start_method = 'forkserver'
        @staticmethod
        def _Popen(process_obj):
            from .popen_forkserver import Popen
            return Popen(process_obj)

    class ForkContext(BaseContext):
        _name = 'fork'
        Process = ForkProcess

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    class ForkServerContext(BaseContext):
        _name = 'forkserver'
        Process = ForkServerProcess
        def _check_available(self):
            if not reduction.HAVE_SEND_HANDLE:
                raise ValueError('forkserver start method not available')

    _concrete_contexts = {
        'fork': ForkContext(),
        'spawn': SpawnContext(),
        'forkserver': ForkServerContext(),
    }
    if sys.platform == 'darwin':
        # bpo-33725: running arbitrary code after fork() is no longer reliable
        # on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
        _default_context = DefaultContext(_concrete_contexts['spawn'])
    else:
        _default_context = DefaultContext(_concrete_contexts['fork'])

else:

    class SpawnProcess(process.BaseProcess):
        _start_method = 'spawn'
        @staticmethod
        def _Popen(process_obj):
            from .popen_spawn_win32 import Popen
            return Popen(process_obj)

    class SpawnContext(BaseContext):
        _name = 'spawn'
        Process = SpawnProcess

    _concrete_contexts = {
        'spawn': SpawnContext(),
    }
    _default_context = DefaultContext(_concrete_contexts['spawn'])

由于本人是win32平台,找到else ,此处定义了两个类,以及两个变量

  1. 变量 _concrete_contexts

    此变量定一个了一个字典,关键字 ‘spawn’对应属性是SpawnContext类的实例

  2. 变量 _default_context

    此变量是DefaultContext类的一个实例,初始化DefaultContext,参数是也是 SpawnContext类的实例,那下面分析SpawnContext的初始化做了哪些工作,以及 初始化DefaultContext都有哪些工作

SpawnContext类

该类中仅仅定义了两个属性,**注意:**Process属性是SpawnProcess类

  1. _name = ‘spawn’
  2. Process = SpawnProcess

SpawnProcess类

​ 这个类只有一个静态方法_Popen,注意:返回的是.popen_spawn_win32.Popen对象

DefaultContext类

​ 代码如下,注意: get_context()返回SpawnContext类的实例

class DefaultContext(BaseContext):
    Process = Process

    def __init__(self, context):
        self._default_context = context
        self._actual_context = None

    def get_context(self, method=None):
        if method is None:
            if self._actual_context is None:
                self._actual_context = self._default_context
            return self._actual_context
        else:
            return super().get_context(method)

Popen类

下面大致分析一下.popen_spawn_win32.Popen类

class Popen(object):
    '''
    Start a subprocess to run the code of a process object
    '''
    method = 'spawn'
	# 此处的 process_obj 正是用户创建的Process类实例
    def __init__(self, process_obj):
        #spawn.get_preparation_data方法的注释大概是
        # 获取子进程 所需 父进程的必要信息
        # spawn对象貌似是解释器加载的时候初始化的东西,目前先不深究
        prep_data = spawn.get_preparation_data(process_obj._name)

        # 创建一个匿名pipe ,返回的二元组是该 pipe读和写的终点?
        rhandle, whandle = _winapi.CreatePipe(None, 0)
        # 根据 这个写句柄 创建一个c语言的运行时 文件描述符
        wfd = msvcrt.open_osfhandle(whandle, 0)
        # 返回用于生成子进程的命令行前缀
        cmd = spawn.get_command_line(parent_pid=os.getpid(),
                                     pipe_handle=rhandle)
        cmd = ' '.join('"%s"' % x for x in cmd)
		# 获取命令行可执行的 python命令字符串
        python_exe = spawn.get_executable()
		
        # bpo-35797: When running in a venv, we bypass the redirect
        # executor and launch our base Python.
        if WINENV and _path_eq(python_exe, sys.executable):
            python_exe = sys._base_executable
            env = os.environ.copy()
            env["__PYVENV_LAUNCHER__"] = sys.executable
        else:
            env = None
		# 以二进制写模式 打开写句柄 
        with open(wfd, 'wb', closefd=True) as to_child:
            # start process
            try:
                hp, ht, pid, tid = _winapi.CreateProcess(
                    python_exe, cmd,
                    None, None, False, 0, env, None, None)
                _winapi.CloseHandle(ht)
            except:
                _winapi.CloseHandle(rhandle)
                raise

            # set attributes of self
            self.pid = pid
            self.returncode = None
            self._handle = hp
            self.sentinel = int(hp)
            self.finalizer = util.Finalize(self, _close_handles,
                                           (self.sentinel, int(rhandle)))

            # send information to child
            set_spawning_popen(self)
            try:
                reduction.dump(prep_data, to_child)
                reduction.dump(process_obj, to_child)
            finally:
                set_spawning_popen(None)

    def duplicate_for_child(self, handle):
        assert self is get_spawning_popen()
        return reduction.duplicate(handle, self.sentinel)

    def wait(self, timeout=None):
        if self.returncode is None:
            if timeout is None:
                msecs = _winapi.INFINITE
            else:
                msecs = max(0, int(timeout * 1000 + 0.5))

            res = _winapi.WaitForSingleObject(int(self._handle), msecs)
            if res == _winapi.WAIT_OBJECT_0:
                code = _winapi.GetExitCodeProcess(self._handle)
                if code == TERMINATE:
                    code = -signal.SIGTERM
                self.returncode = code

        return self.returncode

    def poll(self):
        return self.wait(timeout=0)

    def terminate(self):
        if self.returncode is None:
            try:
                _winapi.TerminateProcess(int(self._handle), TERMINATE)
            except OSError:
                if self.wait(timeout=1.0) is None:
                    raise

    kill = terminate

    def close(self):
        self.finalizer()
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值