您的位置:首页 > 编程语言 > Python开发

python并发编程之多进程

2018-07-11 15:48 447 查看

一 multiprocessing模块介绍

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

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

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

二 Process类的介绍

    创建进程的类

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

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

    参数介绍:

1 group参数未使用,值始终为None
2
3 target表示调用对象,即子进程要执行的任务(或者叫一个功能)
4
5 args表示调用对象的位置参数元组,args=(1,2,'egon',)
6
7 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
8
9 name为子进程的名称(这个我们可以不指定,会有一个默认的名字,也可以通过name自定义子进程的名字)

  方法介绍:

1 p.start():启动进程,并调用该子进程中的p.run()
2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
3
4 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
5 p.is_alive():如果p仍然运行,返回True
6
7 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

    属性介绍:

1 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类的使用

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

 

 四、开启进程的两种方式

方式一:

直接用别人的类,在自定义一个子进程的方法,在调用类时指定该方法名,并根据该方法名是否需要参数为其进行传参,还可以在调用该类时自定义子进程的名子

# # # 方式一(常用方法):
# 强调:以下所有代码都属于父进程代码,开启子进程的过程中会从父进程中拷贝调用类过程中指定的方法,将其内容从父进程中拷贝到子进程中一份,而且两者的修改是相互独立的
from multiprocessing import Process
import time

def task(x):
print('%s is running' %x)
time.sleep(3)
print('%s is done' %x)

if __name__ == '__main__':
# Process(target=task,kwargs={'x':'子进程'})
p=Process(target=task,args=('子进程',)) # 如果args=(),括号内只有一个参数,一定记住加逗号
p.start()            # 只是在操作系统发送一个开启子进程的信号(请求),应用程序管不了,造多长时间,什么时候造应用程序管不了,发信号很快,所以会接着执行下面的代码
# 拷贝父进程的方式是:将父进程这个模块导入一遍(从父进程的内存空间拷贝一份),这个时间已经够我运行完下一行代码了
print('主')

'''
执行结果:
主
子进程 is running
子进程 is done
'''

 

方式二:

通过自定义一个类,并让其继承Process类,并在自定义的类中有一个子进程的run方法

from multiprocessing import Process
import time

class Myprocess(Process):          #继承的父进程中也有一个run方法,可以看源码
def __init__(self,x):
super().__init__()         #初始化的过程中继承父类中的所有属性
self.name=x                #Process内置也有一个name属性,如果不指定__init__方法,name就会继承该类中的name属性,如果自己进行了初始化,就会优先从自己的类中找

def run(self):
print('%s is running' %self.name)    #属性查找:先从对象自己开始找,自己没有到自己的类中找,自己的类中没有到其父类中找,如果都没有就会抛出异常
time.sleep(3)                        #模拟进程运行的时间
print('%s is done' %self.name)

if __name__ == '__main__':         #main以下只属于父进程,子进程拷贝的过程中从不会拷贝该段代码
p=Myprocess('子进程1')         #调用该类传不传参,取决于初始化需不需要参数
p.start()  #p.run()            #会向操作系统发起一个开启子进程的请求,当操作系统开启子进程的时候,并调用该子进程中的p.run()
print('主')                    #父进程要等待子进程的结束才会结束

'''
运行结果:
主
子进程1 is running
子进程1 is done
'''

 

 五、进程内存空间是彼此隔离的

通过修改子进程的一个变量值,看父进程中的有没有改,如果没有该就说明进程内存空间是相互隔离的

from multiprocessing import Process
import time

x=100
def task():
global x
x=0                     #子进程虽然从父进程拷贝了一份数据,但是此时修改的全局变量只是修改子进程自己的全局变量x=100,修改成了x=0,而父进程的全局变量任然是x=100
print('done')           #用来标记子进程代码运行完
if __name__ == '__main__':
p=Process(target=task)  #指定开启子进程的函数
p.start()      #向操作系统发起开启子进程的请求,这个过程需要时间,所以程序会继续往下执行
time.sleep(5)  # 让父进程在原地等待,等了500s后,才执行下一行代码
print(x)       #查看的是父进程的x-----结果任然是:100

 

六、进程对象的方法或属性详解

 join:让父进程在原地等待,等到子进程运行完毕后,才执行下一行代码

from multiprocessing import Process
import time

def task(name):
print('%s is running ' %name)
time.sleep(3)
print('%s is done ' % name)

if __name__ == '__main__':
p=Process(target=task,args=('子进程1',))  #指定子进程的方法,并为其传值
p.start()      #启动子进程:先向操作系统发送请求,等待操作系统开启子进程
p.join()       # 让父进程在原地等待,等到子进程运行完毕后,才执行下一行代码
print('主')

'''
执行结果:
子进程1 is running
子进程1 is done
主           #要等子进程结束才会被执行到,所以最后才被打印出来
'''
View Code

 

 开启多个子进程,使用多个join让父进程等待子进程的结束,花费的总时间比运行最长子进程的时间多一点

from multiprocessing import Process
import time

def task(name,n):
print('%s is running ' %name)
time.sleep(n)
print('%s is done ' % name)

if __name__ == '__main__':
p1=Process(target=task,args=('子进程1',1))        #调用类产生三个对象,并为三个对象指定子进程的方法和需要传的参数
p2=Process(target=task,args=('子进程2',2))
p3=Process(target=task,args=('子进程3',3))

start_time=time.time()
p1.start()                                       #向操作系统发起请求,开启三个子进程
p2.start()
p3.start()
"""
串行:一个程序完完整整的运行完毕,才会运行下一个程序
# 可能你会有疑问:既然join是等待进程结束,那么我像下面这样写,进程不就又变成串行的了吗?
# 当然不是了,必须明确:p.join()是让谁等?
# 很明显p.join()是让主线程等待p的结束,卡住的是主线程而绝非进程p,

# 详细解析如下:
# 进程只要start就会在开始运行了,所以p1-p3.start()时,系统中已经有3个并发的进程了
# 而我们p1.join()是在等p1结束,没错p1只要不结束主线程就会一直卡在原地,这也是问题的关键
# join是让主线程等,而p1-p4仍然是并发执行的,p1.join的时候,其余p2,p3仍然在运行,等#p1.join结束,可能p2,p3早已经结束了,这样p2.join,p3.join直接通过检测,无需等待
# 所以3个join花费的总时间仍然是耗费时间最长的那个进程运行的时间
"""
p3.join()                                        #等待三个子进程结束,才会继续往下执行剩余的代码
p1.join()
p2.join()

stop_time=time.time()
print('主',(stop_time-start_time))              #主进程在等待子进程的结束,自己才会被执行

'''
运行结果:
子进程1 is running
子进程2 is running
子进程3 is running
子进程1 is done
子进程2 is done
子进程3 is done
主 3.722842216491699
'''
View Code

串行的效果,每向操作系统发起一个开启子进程的请求,就执行一次join方法,等待该子进程结束,才会继续执行下一个子进程
from multiprocessing import Process
import time

def task(name,n):
print('%s is running ' %name)
time.sleep(n)
print('%s is done ' % name)

if __name__ == '__main__':
p1=Process(target=task,args=('子进程1',1))
p2=Process(target=task,args=('子进程2',2))
p3=Process(target=task,args=('子进程3',3))

start=time.time()
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()

stop=time.time()
print('主',(stop-start))
View Code

 

为了避免重复写代码,上述启动子进程的代码块可以简写如下
from multiprocessing import Process
import time

def task(name,n):
print('%s is running ' %name)
time.sleep(n)
print('%s is done ' % name)

if __name__ == '__main__':
# p1=Process(target=task,args=('子进程1',1))
# p1.start()
# p2=Process(target=task,args=('子进程2',2))
# p2.start()
# p3=Process(target=task,args=('子进程3',3))
# p3.start()

p_l=[]
start=time.time()
for i in range(1,4):
p=Process(target=task,args=('子进程%s' %i,i))
p_l.append(p)         #每造一个对象就将其添加到空列表中
p.start()

# print(p_l)
for p in p_l:
p.join()

stop=time.time()

print('主',(stop-start))
View Code

 

 

'''pid'''
获取进程的PID,PID就相当于人的身份证号
父进程获取PID直接通过对象点pid即可,而子进程获取自己的PID要导入os块,通过os.getpid()获取子进程的pid,
我们也可以通过在子进程中使用os.getppid()获取父进程的PID

from multiprocessing import Process
import time
import os

def task(n):
print('%s is running ' %os.getpid())
time.sleep(n)
print('%s is done ' % os.getpid())

if __name__ == '__main__':
p1=Process(target=task,args=(10,))
# print(p1.pid)
p1.start()
print(p1.pid) # 父进程内查看子pid的方式
print('主')

#os.getppid()在子进程中查看的是父进程的PID,在父进程中os.getppid()查看的是父进程的爹
from multiprocessing import Process
import time
import os

def task():
print('自己的id:%s 父进程的id:%s ' %(os.getpid(),os.getppid()))
time.sleep(200)

if __name__ == '__main__':
p1=Process(target=task)
p1.start()
print('主',os.getpid(),os.getppid())      #获取父进程爹的PID
# 爹=》主--》儿子

 

了解属性或方法

from multiprocessing import Process,current_process
import time

def task():
print('子进程[%s]运行。。。。' %current_process().name)   #查看当前子进程的名字
time.sleep(200)

if __name__ == '__main__':
p1=Process(target=task,name='子进程1')   #通过name='子进程1'来自定义子进程的名字,不写则以默认的子进程为准
p2=Process(target=task,name='子进程2')   #通过name='子进程2'来自定义子进程的名字,不写则以默认的子进程为准
p1.start()
p2.start()
# print(p1.name)
print('主')

from multiprocessing import Process,current_process
import time

def task():
print('子进程[%s]运行。。。。' %current_process().name)
time.sleep(2)

if __name__ == '__main__':
p1=Process(target=task,name='子进程1')
p1.start()

print(p1.is_alive())             #判断子进程是否存活:True
p1.join()                        #子进程已经运行完毕,此时在其后在查看子进程的存活状态,bool值就为Flase
print(p1.is_alive())             #Flase,此时子进程已经结束

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

 

 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: