【3.0】多进程操作

【一】multiprocessing模块介绍

  • python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。
    • Python提供了multiprocessing。
    • multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
  • multiprocessing模块的功能众多:
    • 支持子进程、通信和共享数据、执行不同形式的同步
    • 提供了Process、Queue、Pipe、Lock等组件。
  • 需要再次强调的一点是:
    • 与线程不同,进程没有任何共享状
    • 进程修改的数据,改动仅限于该进程内。

【二】Process类的介绍

【1】创建进程的类

  • 语法
Process([group [, target [, name [, args [, kwargs]]]]])
  • 由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:

  1. 需要使用关键字的方式来指定参数
  2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

【2】参数介绍

  • group参数未使用,值始终为None

  • target表示调用对象,即子进程要执行的任务

  • args表示调用对象的位置参数元组,args=(1,2,'ly',)

  • kwargs表示调用对象的字典,kwargs=

  • name为子进程的名称

【3】方法介绍

  • p.start()

    • 启动进程,并调用该子进程中的p.run()
  • p.run():

    • 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
  • p.terminate():

    • 强制终止进程p,不会进行任何清理操作
    • 如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。
    • 如果p还保存了一个锁那么也将不会被释放,进而导致死锁
  • p.is_alive():

    • 如果p仍然运行,返回True
  • p.join([timeout]):

    • 主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。

    • timeout是可选的超时时间

    • 需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

【4】属性介绍

  • p.daemon

    • 默认值为False
    • 如果设为True,代表p为后台运行的守护进程
    • 当p的父进程终止时,p也随之终止
    • 并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
  • p.name:

    • 进程的名称
  • p.pid

    • 进程的pid
  • p.exitcode:

    • 进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
  • p.authkey:

    • 进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。
    • 这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

【三】Process类的使用

【1】特别提醒

  • 注意:在windows中Process()必须放到 if __name == 'main__':

  • 详细解释

Since Windows has no fork, the multiprocessing module starts a new Python process **and** imports the calling module. 
If Process() gets called upon import, then this sets off an infinite succession of new processes (**or** until your machine runs out of resources). 
This **is** the reason **for** hiding calls to Process() inside

**if name == "main"**
since statements inside this **if**-statement will **not** get called upon import.
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。 
如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 
这是隐藏对Process()内部调用的原,使用if **name** == “**main** ”,这个if语句中的语句将不会在导入时被调用。

【2】创建并开启子进程的两种方式

(1)方法一:直接使用Process方法

import time
import random
from multiprocessing import Process


# 创建子进程方式一:使用Process方法实例化得到任务对象

# 【一】定义任务函数
def run(name):
    print(f' {name} start sport')
    time.sleep(random.randrange(1, 5))
    print(f' {name} end sport')


if __name__ == '__main__':
    # 【二】创建进程对象
    # Process(target=被调用的任务名(进程名), args=(参数,))
    # args:里面的参数一定要用逗号隔开(容器类型无论里面有几个元素,哪怕只有一个元素,也一定要用逗号隔开)
    p1 = Process(target=run, args=('dream',))  # 必须加,号
    p2 = Process(target=run, args=('hope',))
    p3 = Process(target=run, args=('meng',))
    p4 = Process(target=run, args=('opp',))

    # 【三】开启进程
    # 告诉操作系统帮我们创建一个进程
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    print('这是所有子进程的主进程')
    
    # 一定是主进程先跑起来,再去启动子进程
    # 子进程的执行顺序是不确定的,由操作系统调度
    # 这是所有子进程的主进程
    #  meng start sport
    #  hope start sport
    #  opp start sport
    #  dream start sport
    #  meng end sport
    #  hope end sport
    #  dream end sport
    #  opp end sport

(2)方法二:继承Process类

from multiprocessing import Process
import time
import random


# 方式二:创建一个新的类继承自Process
class MyProcess(Process):
    def __init__(self, name):
        super().__init__()
        self.name = name

    # 【一】定义任务函数
    def run(self):
        print(f' {self.name} start sport')
        time.sleep(random.randrange(1, 5))
        print(f' {self.name} end sport')


if __name__ == '__main__':
    # 【二】创建子进程
    p1 = MyProcess(name='dream')
    p2 = MyProcess(name='hope')
    p3 = MyProcess(name='meng')
    p4 = MyProcess(name='opp')

    # 【三】启动主进程和子进程
    # 告诉操作系统帮我们创建一个进程
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    print('这是所有子进程的主进程')

    # 这是所有子进程的主进程
    #  meng start sport
    #  hope start sport
    #  dream start sport
    #  opp start sport
    #  hope end sport
    #  dream end sport
    #  meng end sport
    #  opp end sport

(3)小结

  • 创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去
  • 一个进程对应在内存中就是一块独立的空间
  • 多个进程对应在内存中就是多块独立的内存空间
  • 进程与进程之间数据默认情况下是无法直接交互的,如果想交互可以借助第三方工具或模块

【3】进程之间的内存空间是隔离的

  • 每一个子进程之间的数据是相互隔离的
  • 在执行子进程代码时,只修改自己子进程内的数据,不会影响到其他的子进程
from multiprocessing import Process

money = 999


def func_name():
    global money  # 局部修改全局变量
    money = 99
    print(f'这是子进程中的数据:>>>>{money}')


if __name__ == '__main__':
    print(f'这是原始数据:>>>>{money}')
    p = Process(target=func_name)
    p.start()
    print(f'这是start后的数据:>>>>{money}')
    p.join()
    print(f'这是join后的数据:>>>>{money}')

    # 每一个子进程 只修改 自己子进程中的数据,而不会影响到其他的子进程
    # 这是原始数据:>>>>999
    # 这是start后的数据:>>>>999
    # 这是子进程中的数据:>>>>99
    # 这是join后的数据:>>>>999

【4】socket通信变成并发的形式

(1)server端

from socket import *
from multiprocessing import Process

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)

def talk(conn,client_addr):
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break

if __name__ == '__main__': #windows下start进程一定要写到这下面
    while True:
        conn,client_addr=server.accept()
        p=Process(target=talk,args=(conn,client_addr))
        p.start()

(2)多个client端

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))

这么实现有没有问题???

每来一个客户端,都在服务端开启一个进程,如果并发来一个万个客户端,要开启一万个进程吗,你自己尝试着在你自己的机器上开启一万个,10万个进程试一试。
解决方法:进程池

【5】Process对象的join方法

  • 将并行转为串行

  • join:主进程等,等待子进程结束

(1)非join方法 -- 并行

# 方式二
from multiprocessing import Process
import time


def task(task_name, task_time):
    print(f'这是进程:>>>>{task_name} 需要运行:>>>>{task_time}s')
    time.sleep(task_time)
    print(f'这是进程:>>>>{task_name} 执行完毕')


if __name__ == '__main__':
    # 初始化进行列表
    p_list = []

    # (1)创建进程对象
    # Process(target=被调用的任务名(进程名), args=(参数,))
    # args:里面的参数一定要用逗号隔开(容器类型无论里面有几个元素,哪怕只有一个元素,也一定要用逗号隔开)
    for i in range(1, 4):
        p = Process(target=task, args=(i, i,))
        p_list.append(p)

    start_time = time.time()
    # (2)开启进程
    # 告诉操作系统帮我们创建一个进程
    # (3)主进程等待子进程 p 运行结束后再继续往后执行
    # (3.1) 串行
    for p in p_list:
        p.start()
    print(f'这是进程的主程序')
    print(f'这是程序运行的总耗时:{time.time() - start_time}')

    # 并行 ---- 先将进程启动起来  再 由 异步回调机制 拿到结果
    # 这是进程的主程序
    # 这是程序运行的总耗时:0.05299973487854004
    # 这是进程:>>>>1 需要运行:>>>>1s
    # 这是进程:>>>>2 需要运行:>>>>2s
    # 这是进程:>>>>3 需要运行:>>>>3s
    # 这是进程:>>>>1 执行完毕
    # 这是进程:>>>>2 执行完毕
    # 这是进程:>>>>3 执行完毕

(2)join方法 -- 串行

# 方式一
from multiprocessing import Process
import time


def task(task_name, task_time):
    print(f'这是进程:>>>>{task_name} 需要运行:>>>>{task_time}s')
    time.sleep(task_time)
    print(f'这是进程:>>>>{task_name} 执行完毕')


if __name__ == '__main__':
    # 初始化进行列表
    p_list = []

    # (1)创建进程对象
    # Process(target=被调用的任务名(进程名), args=(参数,))
    # args:里面的参数一定要用逗号隔开(容器类型无论里面有几个元素,哪怕只有一个元素,也一定要用逗号隔开)
    for i in range(1, 4):
        p = Process(target=task, args=(i, i,))
        p_list.append(p)

    start_time = time.time()
    # (2)开启进程
    # 告诉操作系统帮我们创建一个进程
    # (3)主进程等待子进程 p 运行结束后再继续往后执行
    # (3.1) 串行
    for p in p_list:
        p.start()
        p.join()
    print(f'这是进程的主程序')
    print(f'这是程序运行的总耗时:{time.time() - start_time}')
    
    # 依次执行每一个子进程
    # 这是进程:>>>>1 需要运行:>>>>1s
    # 这是进程:>>>>1 执行完毕
    # 这是进程:>>>>2 需要运行:>>>>2s
    # 这是进程:>>>>2 执行完毕
    # 这是进程:>>>>3 需要运行:>>>>3s
    # 这是进程:>>>>3 执行完毕
    # 这是进程的主程序
    # 这是程序运行的总耗时:6.417520761489868

(3)join方法 -- 并行

# 方式三
from multiprocessing import Process
import time


def task(task_name, task_time):
    print(f'这是进程:>>>>{task_name} 需要运行:>>>>{task_time}s')
    time.sleep(task_time)
    print(f'这是进程:>>>>{task_name} 执行完毕')


if __name__ == '__main__':
    # 初始化进行列表
    p_list = []

    # (1)创建进程对象
    # Process(target=被调用的任务名(进程名), args=(参数,))
    # args:里面的参数一定要用逗号隔开(容器类型无论里面有几个元素,哪怕只有一个元素,也一定要用逗号隔开)
    for i in range(1, 4):
        p = Process(target=task, args=(i, i,))
        p_list.append(p)

    start_time = time.time()
    # (2)开启进程
    # 告诉操作系统帮我们创建一个进程
    # (3)主进程等待子进程 p 运行结束后再继续往后执行
    # (3.1) 串行
    # 初始化任务列表
    task_list = []
    # 将所有任务添加到任务列表里
    for p in p_list:
        p.start()
        task_list.append(p)

    # 循环开始每一个任务
    for task in task_list:
        task.join()
    print(f'这是进程的主程序')
    print(f'这是程序运行的总耗时:{time.time() - start_time}')
    
    # join 方法的正确使用方法 : 多进程并行,总耗时为耗时最长的进程
    # 这是进程:>>>>1 需要运行:>>>>1s
    # 这是进程:>>>>2 需要运行:>>>>2s
    # 这是进程:>>>>3 需要运行:>>>>3s
    # 这是进程:>>>>1 执行完毕
    # 这是进程:>>>>2 执行完毕
    # 这是进程:>>>>3 执行完毕
    # 这是进程的主程序
    # 这是程序运行的总耗时:3.323244571685791

【四】Process对象的其他方法或属性(了解)

【1】查看当前进程的进程号

(1)引入

(1)什么是进程号

  • 一台计算机上面运行着很多进程,那么计算机是如何区分并管理这些进程服务端呢?
    • 计算机会给每一个运行的进程分配一个PID号

(2)如何查看进程号?

  • Windows系统
    • CMD 命令行 tasklist 即可查看
  • Mac系统
    • 终端运行 ps aux 即可查看

(3)如何根据指定进程号查看进程

  • Mac系统
    • 终端运行 ps aux|grep PID 即可查看
  • Windows系统
    • CMD 命令行 tasklist |findstr PID 即可查看

(2)查看当前进程的进程号current_process().pid 方法

from multiprocessing import Process, current_process
import time


def task():
    # 查看当前进程的 进程(PID) 号
    print(f'当前程序:>>>>{current_process().pid} 正在运行')
    time.sleep(2)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()

    print(f'这是主程序:>>>{current_process().pid}')

    # 这是主程序:>>>11168
    # 当前程序:>>>>10944 正在运行

(3)查看当前进程的进程号os.getpid() 方法

from multiprocessing import Process
import os
import time


def task():
    # 查看当前进程的 进程(PID) 号
    print(f'当前程序:>>>>{os.getpid()} 正在运行')
    time.sleep(2)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()

    print(f'这是主程序:>>>{os.getpid()}')

    # 这是主程序:>>>9928
    # 当前程序:>>>>3912 正在运行

(4)查看当前进程的父进程的进程号os.getppid() 方法

from multiprocessing import Process
import os
import time


def task():
    # 查看当前进程的 进程(PID) 号
    print(f'当前程序:>>>>{os.getpid()} 正在运行')
    # 查看当前进程的 父进程(PID) 号
    print(f'当前程序的父进程:>>>>{os.getppid()} 正在运行')
    time.sleep(2)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()

    print(f'这是主程序:>>>{os.getpid()}')
    print(f'这是主程序的父进程:>>>{os.getppid()}')

    # 这是主程序:>>>22236
    # 这是主程序的父进程:>>>17720
    # 当前程序:>>>>3756 正在运行
    # 当前程序的父进程:>>>>22236 正在运行

【2】杀死当前进程p.terminate()

  • 告诉操作系统帮我去杀死当前进程
  • 但是需要一定的时间。
  • 代码的运行速度极快

【3】判断当前进程是否存活p.is_alive()

from multiprocessing import Process
import os
import time


def task():
    # 查看当前进程的 进程(PID) 号
    print(f'当前程序:>>>>{os.getpid()} 正在运行')
    print(f'当前程序的父进程:>>>>{os.getppid()} 正在运行')

    time.sleep(2)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()

    # 杀死当前进程 - 需要给操作系统一点缓冲时间
    p.terminate()
    time.sleep(0.2)
    # 判断当前进程是否存活
    print(p.is_alive())

    print(f'这是主程序:>>>{os.getpid()}')
    print(f'这是主程序的父进程:>>>{os.getppid()}')

    # 主进程正常
    # 调用程序中的子进程被杀死
    # False
    # 这是主程序:>>>16796
    # 这是主程序的父进程:>>>17720
  • 一般默认会将
  • 存储布尔值的变量名
  • 返回的结果是布尔值的方法名
  • 起成is_ 开头的变量名
posted @ 2024-01-23 14:21  Chimengmeng  阅读(32)  评论(0编辑  收藏  举报