06.系统编程-1.进程
Posted cjr0707
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了06.系统编程-1.进程相关的知识,希望对你有一定的参考价值。
1、多任务的引入
实现类似唱歌又跳舞的任务同时进行的,叫做:多任务
2、多任务的概念
什么叫“多任务”。简单地说, 就是操作系统可以同时运行多个任务。
单核CPU执行多任务,就是操作系统轮流让各个任务交替执行,任务1执行0.01秒, 切换到任务2,任务2执行0.01秒,再切换到任务3, 执行0.01秒……这样反复执行下去。每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了, 我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
3、进程的创建-fork
3.1进程vs程序
编写完毕的代码,在没有运行的时候,称之为程序。
正在运行着的代码,就成为进程。
进程,除了包含代码以外,还有需要运行的环境等,所以和程序是有区别的。
3.2fork( )
os模块封装了fork函数,可以在Python程序中轻松创建子进程
import os
#注意,fork函数,只在Unix/Linux/Mac上运行,Windows不可以
pid = os.fork()
if pid == 0:
print(‘哈哈111‘)
else:
print(‘哈哈222‘)
说明
- 程序执行到os.fork()时,操作系统会创建一个新的进程(子进程),然后复制父进程的所有信息到子进程中
- 父进程和子进程都会从fork()函数中得到一个返回值,在子进程中这个值一定是0,而父进程中是子进程的 id号。
- 在Unix/Linux操作系统中,提供了一个fork()系统函数,它非常特殊。
- 普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。
- 这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
3.3getpid()、getppid()
可以理解为getppid——get parents pid 的缩写
import os
rpid = os.fork()
if rpid<0:
print("fork调用失败。")
elif rpid == 0:
print("我是子进程(%s),我的父进程是(%s)"%(os.getpid(),os.getppid()))
else:
print("我是父进程(%s),我的子进程是(%s)"%(os.getpid(),rpid))
print("父子进程都可以执行这里的代码")
运行结果:
我是父进程(19360),我的子进程是(19361)
父子进程都可以执行这里的代码
我是子进程(19361),我的父进程是(19360)
父子进程都可以执行这里的代码
4、多进程修改全局变量
#coding=utf-8
import os
import time
num = 0
# 注意, fork函数, 只在Unix/Linux/Mac上运行, windows不可以
pid = os.fork()
if pid == 0:
num+=1
print(‘哈哈1---num=%d‘%num)
else:
time.sleep(1)
num+=1
print(‘哈哈2---num=%d‘%num)
总结:
- 多进程中,子进程相当于是主进程的一个深拷贝
- 每个进程中所有数据(包括全局变量)
- 都各有拥有一份,互不影响
5、多次fork问题
如果在一个程序,有2次的fork函数调用, 是否就会有3个进程呢?
#coding=utf-8
import os
import time
# 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以
pid = os.fork()
if pid == 0: #if下是子进程的操作,因为子进程的返回值永远是0
print(‘haha...1,我是%s,我的父进程是%s‘%(os.getpid(),os.petppid))
else: #else下是主进程操作,因为主进程的返回值是其子进程的pid
print(‘haha...,我是主进程%s‘%os.getpid)
pid = os.fork()
if pid == 0:
print(‘haha...2,我是%s,我的父进程是%s‘%(os.getpid(),os.petppid))
else:
print(‘haha...,我是主进程%s‘%os.getpid)
time.sleep(1)
运行结果:
haha...,我是主进程9584
haha...,我是主进程9584
haha2...,我是9586,我的父进程是9584
haha1...,我是9585,我的父进程是9584
haha...,我是主进程9585
haha2...,我是9587,我的父进程是9585
仔细看上述代码和运行结果,程序的执行结果并不是你想象的打印了四次,在这里需要说明的是,当主进程(9584)执行到第一条fork语句时,要创建一个子进程,在这个时候,会将主进程的所有内容,复制一份给子进程(9585),也就说,子进程(9585)中也会包含主进程(9584)中的第二条fork语句,即子进程(9585)也会再创建子进程(9587),主进程(9584)执行到第二条fork语句时,会再创建一个子进程(9586),这样就导致了这段代码会有六次的print,这样说可能依然不是很清楚,下面画一个简单的进程关系图帮助理解:
graph LR
主进程9584-->子进程9585
子进程9585-->子进程9587
主进程9584-->子进程9586
说明:
主进程、 子进程的执行顺序没有规律, 完全取决于操作系统的调度算法
接下来的在Windows、Linux中都可以执行多进程
6、multiprocessing
multiprocessing模块就是跨平台版本的多进程模块。
multiprocessing模块提供了一个Process类来代表一个进程对象。
Process语法结构如下:
Process([group [, target [, name [, args [, kwargs]]]]])
- target:表示这个进程实例所调用对象;
- args:表示调用对象的位置参数元组;
- kwargs:表示调用对象的关键字参数字典;
- name: 为当前进程实例的别名;
- group:多数情况下用不到;
6.1Process类常用方法
- is_alive():判断进程实例是否还在执行;
- join([timeout]):可以等待进程结束后再继续往下运行,或等待多少秒,通常用于进程间的同步;
- start():启动进程实例( 创建子进程) ;
- run():如果没有给定target参数, 对这个对象调用start()方法时, 就将执行对象中的run()方法;
- terminate():不管任务是否完成,即终止;
6.2Process类常用属性
- name: 当前进程实例别名, 默认为Process-N, N为从1开始递增的整数;
- pid: 当前进程实例的PID值;
6.3使用multiprocessing里的Process创建进程的第一种方式
from multiprocessing import Process
from time import sleep
#子进程
def sing(param1,param2,**kwargs):
print("%s,%s"%(param1,param2))
print(kwargs)
k = 0
while k<5:
print("sing %d..."%k)
sleep(1)
k +=1
if __name__ == ‘__main__‘:
print("main process run")
#定义子进程:唱歌
p = Process(target=sing,args=(20,"xxx"),kwargs={"key1":"mmm","key2":"nnn"})
#子进程的启动
p.start()
#p.join() 如果加上加上join主进程就不会进入while循环,要等到子进程运行结束
#不加的话,子进程与主进程互不干扰,都在执行
#主进程:玩游戏
while True:
print("gaming...")
#停止0.5秒
sleep(0.5)
print("child process is over")
print("main poress end")
运行结果:
main process run
gaming...
20,xxx
{‘key2‘: ‘nnn‘, ‘key1‘: ‘mmm‘}
sing 0...
gaming...
gaming...
sing 1...
gaming...
gaming...
7、使用multiprocessing里的Process创建进程的第二种方式——继承Process的类
- 创建新的进程还能够使用类的方式,可以自定义一个类, 继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象
from multiprocessing import Process
from time import sleep
#定义进程类,继承Process
class Music(Process):
def __init__(self,name):
#调用父类的初始化方法
#因为Process类本身也有__init__方法, 这个子类相当于重写了这个方法
#但这样就会带来一个问题,我们并没有完全的初始化一个Process类,所以就不能使用从这个
#最好的方法就是将继承类本身传递给Process.__init__方法,完成这些初始化操作
Process.__init__(self)
self.name = name
#重写Process类的run方法
def run(self):
print(self.name)
k = 0
while True:
print("sing %d..." % k)
sleep(0.5)
k += 1
if __name__ == ‘__main__‘:
print("main process run")
#创建新的进程,定义进程对象
p = Music(‘xxx‘)
#对一个不包含target属性的Process类执行start()方法
#就会运行这个类中的run()方法
p.start()
# 主进程:玩游戏
while True:
print("gaming...")
# 停止0.5秒
sleep(0.5)
运行结果:
main process run
gaming...
xxx
sing 0...
gaming...
sing 1...
gaming...
sing 2...
gaming...
……
8、进程池 Pool
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行
8.1常用函数
multiprocessing.Pool常用函数解析:
- apply_async(func[, args[, kwds]]):使用非阻塞方式调用func(并执行,堵塞方式必须等待上一个进程退出才能执行下一个进程)
【异步:一个任务不用等待其他任务执行完毕再执行,可以立刻开始,互不影响】 - args为传递给func的参数列表;
- kwds为传递给func的关键字参数列表;
- apply(func[, args[, kwds]]): 使进程阻塞方式调用func
- close():关闭Pool,使其不再接受新的任务;【协同步调,按照一定的顺序执行】
- terminate():不管任务是否完成,立即终止;
- join():主进程阻塞,等待子进程的退出,必须在close或terminate之后。
8.2apply堵塞式
from multiprocessing import Pool
from time import sleep
def kill():
i=0
while i<10:
print("乔峰杀了一个挑战者...")
sleep(1)
i += 1
def attack(name):
print("%s attack..."%name)
sleep(1)
if __name__ == ‘__main__‘:
#创建进程池,最大进程数5
po = Pool(5)
#向进程池添加子进程
#Pool.apply_async(要调用的目标,(传递给目标的参数元组,))
#每次循环将会用空闲出来的子进程去调用目标
po.apply_async(kill)
#po.apply(kill)
for i in range(20):
po.apply_async(attack,(‘掌门__‘+str(i),))
#po.apply(attack,(‘zhangmen_‘+str(i),))
po.close() #关闭进程池, 关闭后po不再接收新的请求
po.join() #等待po中所有进程执行完成, 必须放在close语句之后
#po.terminate()
执行代码:
乔峰杀了一个挑战者...
掌门__0 attack...
掌门__1 attack...
掌门__2 attack...
掌门__3 attack...
乔峰杀了一个挑战者...
掌门__4 attack...
掌门__5 attack...
掌门__6 attack...
掌门__7 attack...
……
如果不使用 po.apply_async
而使用 po.apply
其代码执行如下:
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
掌门__0 attack...
掌门__1 attack...
掌门__2 attack...
……
9、进程间的通信Queue
Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信。可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息列队程序。
说明:
1、初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);
2、Queue.qsize():返回当前队列包含的消息数量;
3、Queue.empty():如果队列为空,返回True, 反之False ;
4、Queue.full():如果队列满了,返回True,反之False;
5、Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;Queue.get_nowait():相当Queue.get(False);
-----a) 如果block使用默认值, 且没有设置timeout( 单位秒) , 消息列队如果为空, 此时程序将被阻塞( 停在读取状态) , 直到从消息列队读到消息为止,如果设置了timeout, 则会等待timeout秒, 若还没读取到任何消息, 则抛出"Queue.Empty"异常;
-----b) 如果block值为False, 消息列队如果为空, 则会?刻抛出"Queue.Empty"异常;
6、Queue.put(item,[block[,timeout]]):将item消息写入队列,block默认值为True;Queue.put_nowait(item):相当Queue.put(item, False);
-----a) 如果block使用默认值, 且没有设置timeout( 单位秒) , 消息列队如果已经没有空间可写入, 此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒, 若还没空间,则抛出"Queue.Full"异常;
-----b) 如果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;
import time, random
from multiprocessing import Process, Queue
def write(q):
print(‘write...%s‘ % (id(q)))
for value in [‘A‘, ‘B‘, ‘C‘]:
print(‘Put %s to queue...‘ % value)
q.put(value)
time.sleep(random.random())
def read(q):
print(‘read...%s,num=%s,flag = %s‘ % (id(q), q.qsize(), q.empty()))
while True:
if not q.empty():
value = q.get()
print(‘Get %s from queue.‘ % value)
time.sleep(3)
if __name__ == ‘__main__‘:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 等待pw结束:
# pw.join()
# 启动子进程pr,读取:
pr.start()
# pr.join()
print(‘game over...‘)
执行结果:
game over...
write...2800150905688
Put A to queue...
read...2215658980184,num=1,flag = False
Get A from queue.
Put B to queue...
Put C to queue...
Get B from queue.
Get C from queue.
9.1Queue实例
from multiprocessing import Queue
q = Queue(3)
print(q.empty())
q.put(33)
print(q.empty())
print(q.qsize())
q.put(55)
print(q.full())
print(q.qsize())
q.put(55)
print(q.full())
print(q.qsize())
print(q.get())
print(q.full())
print(q.qsize())
运行结果:
True
False
1
False
2
True
3
33
False
2
9.2进程池中的Queue
如果要使用Pool创建进程, 就需要使用multiprocessing.Manager()中的Queue(), 而不是multiprocessing.Queue(), 否则会抛异常。
import time,random,os
from multiprocessing import Pool,Manager
def reader(q):
print("reader启动(%s),父进程为(%s)"%(os.getpid(),os.getppid()))
for i in range(q.qsize()):
print("reader从Queue获取到消息:%s"%q.get())
def writer(q):
print("writer启动(%s),父进程为(%s)"%(os.getpid(),os.getppid()))
for i in "yongGe":
q.put(i)
if __name__=="__main__":
print("(%s) start"%os.getpid())
q=Manager().Queue() #使用Manager中的Queue来初始化
po=Pool()
# 使用阻塞模式创建进程
# 这样就不需要在reader中使用死循环了,可以让writer完全执行完成后
# 再用reader去读取
po.apply(writer,(q,))
po.apply(reader,(q,))
po.close()
po.join()
print("(%s) End"%os.getpid())
运行结果:
(11484) start
writer启动(10184),父进程为(11484)
reader启动(10256),父进程为(11484)
reader从Queue获取到消息:y
reader从Queue获取到消息:o
reader从Queue获取到消息:n
reader从Queue获取到消息:g
reader从Queue获取到消息:G
reader从Queue获取到消息:e
(11484) End
以上是关于06.系统编程-1.进程的主要内容,如果未能解决你的问题,请参考以下文章