并发编程之多进程操作篇

image

引言

Python中的多进程是通过multiprocessing包来实现的,和多线程的threading.Thread差不多,它可以利用multiprocessing.Process对象来创建一个进程对象。这个进程对象的方法和线程对象的方法差不多也有start(), run(), join()等方法,其中有一个方法不同Thread线程对象中的守护线程方法是setDeamon,而Process进程对象的守护进程是通过设置daemon属性来完成的。

介绍

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。

multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

Process类的介绍

首先,我们来看看这个类的原型

class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={})
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数说明:

target:表示调用对象,一般为函数,也可以为类。
args:表示调用对象的置参数元组。
kwargs:表示调用对象的字典。
name:为进程的别名。
group:参数不使用,值始终为None。

类提供的常用方法

is_alive():返回进程是否是激活的。
join([timeout]) :可以等待子进程结束后再继续往下运行,通常用于进程间的同步。进一步地解释,哪个子进程调用了join方法,主进程就要等该子进程执行完后才能继续向下执行。
run() :代表进程执行的任务函数,可被重写。
start() :激活进程。
terminate():终止进程。

属性

authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可).
daemon:父进程终止后自动终止而不会等待子进程,且自己不能产生新进程,必须在start()之前设置。
exitcode:退出码,进程在运行时为None,如果为–N,表示被信号N结束。
name:获取进程名称.
pid:进程id

Process类的使用

注意:在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语句中的语句将不会在导入时被调用。

开启进程的两种方式

# 第一种方式 直接使用Process方法
from multiprocessing import Process
import time
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
if __name__ == '__main__':
# 1. 创建一个对象
p = Process(target=task, args=('xiao',))
# 容器类型哪怕里面只有1个元素,建议要用逗号隔开
# 2. 开启进程
p.start() # 告诉操作系统帮你创建一个进程 异步提交
print('main')
"""
windows 操作系统下,创建进程一定要在main内创建
因为windows下创建进程类似于模块导入的方式
会从上往下依次执行代码
linux中则是将代码完完整整的拷贝一份
"""
# 第二种方式 继承Process类
import time
from multiprocessing import Process
class MyProcess(Process):
def run(self):
print('hello world')
time.sleep(3)
print('get out')
if __name__ == '__main__':
p = MyProcess()
p.start()
print('main')

总结

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

Process对象的join方法

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

进程间数据是相互隔离的(默认情况下)

from multiprocessing import Process
money = 100
def task():
global money # 局部修改全局
money = 666
print('zi', money)
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.join()
print(money)

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_ 开头的变量名

人工智能相关参考网站

图灵机器人-智能好用的AI对话机器人 (turingapi.com)

百度AI开放平台-全球领先的人工智能服务平台 (baidu.com)

posted @   Xiao0101  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示

目录