进程和多进程
进程
概念:
-
进程(Process)是计算机中的程序关于某数据 集合上的一次运行活动,是系统进行资源分配的基本单 位。
-
多个进程同时执行时,每个进程的执行都需要由操作系 统按一定的算法(RR调度、优先数调度算法等)分配 内存空间。
组成:
进程控制块PCB、数据段、正文段
基本状态:
就绪状态、运行状态和阻塞状
创建:
用户创建出来的所有进程都是由操作系统负责,新进程的创建都是由一个已经存在的进程执 行了一个用于创建进程的系统调用而创建的 Linux中pid为0的进程,是所有进程的主进程
如何创建子进程?
在python中,每一个运行的程序都有一个主进程,可以利用模块中封装的方法来创建子进程 (os.fork =>linux、multiprocessing)
OS.fork 创建子进程
os.fork就是用来创建子进程的方法,os.fork中就用来创建子进程的方法
- 使用fork创建子进程后,操作系统会将当前的进程复制一份
- 原来的进程称为父进程,新创建的进程称为子进程
- 两个进程会各自互不干扰的执行下面的程序
- 父进程与子进程的执行顺序与系统调度有关
- 在子进程内,这个方法会返回0;在父进程内,这个方法会返回子进程的编号PID
返回值
- 返回值为大于0时,此进程为父进程,且返回的数字为子进程的PID;
- 当返回值为0时,此进程为子进程
- 如果返回值为负数则表明创建子进程失败
- 父进程结束时,子进程并不会随父进程立刻结束。同样,父进程不会等待子进程执行完
os.getpid():获取进程的进程号
os.getppid():获取父进程的进程号
import os,time
print("start...")
pid = os.fork()
#父进程运行时得到的pid为子进程的pid,子进程运行时这个pid就是0
print("outerside pid is:",pid)
if pid == 0:
print("child process")
time.sleep(60)
print("child pid is:",os.getpid())
print("child-parent pid is:",os.getpid())
else:
print("parent process")
time.sleep(60)
print("parent pid is:",os.getpid())
孤儿进程
父进程退出,子进程还在运行,那么这个子进程就会成为孤儿进程,孤儿进程会被怕pid为1 的进程收养
[root@Hardy 8_10]# ps -ef|grep python3
root 1679 1 0 15:12 pts/0 00:00:00 python3 process1.py
root 1685 1545 0 15:13 pts/1 00:00:00 grep --color=auto python3
僵尸进程
子进程退出,父进程没有响应。父进程没有调用wait()或者waitpid去获取子进程的状态,子进程的进程控制块就会依然保存在系统中国,这种进程就称之为僵尸进程
[root@Hardy 8_10]# ps aux|grep python3
root 1663 0.0 0.3 124932 5692 pts/0 S+ 15:06 0:00 python process1.py
root 1664 0.0 0.0 0 0 pts/0 Z+ 15:06 0:00 [python3] <defunct>
root 1666 0.0 0.0 112824 976 pts/1 S+ 15:06 0:00 grep --color=auto python3
multiprocessing.Process
Multiprocessing 由于windows没有fork调用,python提供了multiprocessing支持跨平台版本。
创建管理进程模块:
- Process(用于创建进程)
- Pool(用于创建管理进程池)
- Queue(用于进程通信,资源共享)
- Value,Array(用于进程通信,资源共享)
- Pipe(用于管道通信)
- Manager(用于资源共享)
Process类
构造方法:Process([group [, target [, name [, args [, kwargs]]]]])
- group: 线程组,目前还没有实现,库引用中提示必须是None;
- target: 要执行的方法;
- name: 进程名;
- args/kwargs: 要传入方法的参数
实例方法:
- p.start():启动进程,并调用该子进程中的p.run()
- p.run(): strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法
- p.terminate(): 不管任务是否完成,立即停止工作进程
- p.is_alive(): 如果p仍然运行,返回True
- p.join([timeout]): 阻塞当前上下文环境的进程,直到调用此方法的进程终止或到达指定的timeout
import multiprocessing
from multiprocessing import Process,current_process
import time
lst = []
def task(i):
print(current_process().name,i,'start...')
time.sleep(2)
lst.append(i)
print(lst)
print(current_process().name,i,'end....')
if __name__ == "__main__":
p_lst = []
for i in range(4):
p = Process(target = task,args = (i,)) #只能在程序入口创建多进程
p_lst.append(p)
p.start()
print("st")
for p in p_lst:
p.join()
print("main end....")
# class Myprocess(multiprocessing.Process):
# def __init__(self,num):
# super(Myprocess,self).__init__()
# self.num = num
# def run(self):
# print(current_process().name)
# print(f"running on process:{self.num}")
#
# if __name__ == "__main__":
# t1 = Myprocess(1)
# t2 = Myprocess(2)
# t1.start()
# t2.start()
多进程 – 数据共享
不同进程间内存是不共享的,multiprocessing中提供以下方式实现进程间的数据交换
- Queue(用于进程通信,资源共享)
- Value,Array(用于进程通信,资源共享)
- Pipe(用于管道通信)
- Manager(用于资源共享)
使用multiprocessing.Array共享数据
- 创建Array时,需要指定数据类型
- 如:arr = Array(‘i’ , [11, 22, 33, 44])
- 'i’表示数据类型:“d”表示一个双精度的浮点数, “i”表示一个有符号的整数
使用multiprocessing.Manager共享数据
- 由Manager()返回的manager提供 list, dict, Namespace, Lock, RLock, Semaphore,
- BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array类型的支持。
- Manager比Array要好用一点,因为它可以同时保存多种类型的数据格式
from multiprocessing import Manager,Process,Lock
import time
def func(i,temp):
with lock:
time.sleep(1)
temp[0] += 100
# time.sleep(1)
print(i,"-------->",temp[0])
#使用manager 父进程要等待子进程结束再退出
lock = Lock()
if __name__ == "__main__":
manager = Manager()
temp = manager.list([1,2,3])
p_list = []
for i in range(10):
p = Process(target=func,args=(i,temp))
p.start()
p_list.append(p)
for i in p_list:
i.join() #不加join,manager进程会先退出,子进程就访问不到manager共享的数据了
使用multiprocessing.Queue共享数据
- 消息队列:multiprocessing.Queue
Queue是对进程安全的队列,可以使用Queue实现对进程之间的数据传输;还有一个重要作用是作
为缓存使用。
Queue(maxzize = 0) 创建一个队列对象,maxsize 表示队列中最多存放消息的数量。 - 实例方法:
- put(obj [, block=True[, timeout]]):调用队列对象的put()方法将obj插入到队列中
- get([block=True[, timeout]]):get方法可以将队列中读取并删除一个元素
- full():判断队列是否为满
- empty():判断队列是否为空
- qsize():获取队列中消息数量
Queue不能再Pool进程池中使用,使用Multiprocessing.Manager类可以适用Pool类 - 不一致读
- 为了防止和多线程一样的出现数据抢夺和脏数据的问题,同样需要设置进程锁。与threading类 似,在multiprocessing里也有同名的锁类RLock, Lock, Event, Condition, Semaphore,连用法 都是一样样的!
- 当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值
#队列
from multiprocessing import Process,Queue
import time
def func(i,q):
if not q.empty():
print(i,"----->get value,",q.get())
time.sleep(2)
#先进先出
if __name__ == "__main__":
q = Queue() #不能用在进程池
for i in range(6):
q.put(10-i)
p = Process(target=func,args=(i,q))
p.start()
multiprocessing.Pool
多进程资源消耗
一般我们是通过动态创建子进程(或子线程)来实现并发服务器的,但是会存在这样一些缺点:
- 动态创建进程(或线程)比较耗费时间,这将导致较慢的服务器响应。
- 动态创建的子进程通常只用来为一个客户服务,这样导致了系统上产生大量的细微进程(或
线程)。进程和线程间的切换将消耗大量CPU时间。 - 动态创建的子进程是当前进程的完整映像,当前进程必须谨慎的管理其分配的文件描述符和
堆内存等系统资源,否则子进程可能复制这些资源,从而使系统的可用资源急剧下降,进而
影响服务器的性能。
所以呢,就引入了进程池的概念
Pool 进程池
- 进程池的作用:有效的降低频繁创建销毁线程所带来的额外开销。
进程池的原理:
- 进程池都是采用预创建的技术,在应用启动之初便预先创建一定数目的进程。
- 应用在运行的过程中,需要时可以从这些进程所组成的进程池里申请分配一个空闲的进程,来执
行一定的任务,任务完成后,并不是将进程销毁,而是将它返还给进程池,由线程池自行管理。 - 如果进程池中预先分配的线程已经全部分配完毕,但此时又有新的任务请求,则进程池会动态的
创建新的进程去适应这个请求。 - 某些时段应用并不需要执行很多的任务,导致了进程池中的线程大多处于空闲的状态,为了节省
系统资源,进程池就需要动态的销毁其中的一部分空闲进程。 - 进程需要一个管理者,按照一定的要求去动态的维护其中进程的数目
Pool 主进程管理进程的机制:
- 最简单、最常用的算法是随机算法和Round Robin(轮流算法)
- 主进程和所有子进程通过一个共享的工作队列来实现同步:子进程都睡眠在该工作队列上,当有
新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。 - 当选择好子进程后,主线程程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。我们可以把这些数据定义为全局,那么它们本身就是被所有进程共享的。对于进程池而言,最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信
Pool 进程池的应用场景
- 需要大量的进程来完成任务,且完成任务的时间比较短。
- 但对于长时间的任务,比如一个Telnet连接请求,进程池的优点就不明显了。因为Telnet会话时
间比线程的创建时间大多了
Pool 类
构造方法
- Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])
- processes :使用的工作进程的数量,如果processes是None那么使用 os.cpu_count()返回的数量。
- initializer: 如果initializer是None,那么每一个工作进程在开始的时候会调用initializer(*initargs)。
- maxtasksperchild:工作进程退出之前可以完成的任务数,完成后用一个新的工作进程来替代原进程,来让闲置的资源被释放maxtasksperchild默认是None,意味着只要Pool存在工作进程就会一直存活。
实例方法
- apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞。
- apply(func[, args[, kwds]])是阻塞的。
- close() 关闭pool,使其不在接受新的任务。
- terminate() 关闭pool,结束工作进程,不在处理未完成的任务。
- join() 主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。这样是因为被终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程
注意:
① 使用Pool创建进程池对象,同时进程池中进程已经启动
② 向进程池对象中添加事件,事件排队执行
③ 如果主进程退出,则进程池中所有进程都退出
from multiprocessing import Pool,current_process
import time
lst = []
def task(i):
print(current_process().name,i,'start...')
time.sleep(2)
lst.append(i)
print(lst)
print(current_process().name,i,'end....')
if __name__ == "__main__":
#创建一个进程池,建议进程数和cpu核数一致即可,也可也等于alter,cpu几核就几个
#maxtasksperchild=3,每个work进程最多只能处理三个任务,执行完就挂了
#释放内存空间
p = Pool(processes=4,maxtasksperchild=3)
# time.sleep(100)
for i in range(20):
#进程池接受任务
p.apply_async(func=task,args=(i,))
#关闭进程池,不接受新任务,已经创建的任务会继续运行
p.close()
#等待子进程执行完毕,父进程再执行
p.join()
print("end...........")
Python中多进程与多线程
多进程与多线程的选择
- io密集型计算用多线程
- cpu密集型计算用多进程