程序和进程的区别
程序:像xiaogu.py这样的文件叫程序,程序是静态的
进程:运行起来的程序+用到的资源叫进程,进程是操作系统分配资源的最小的单位
多进程
python已经封装好了底层模块multiprocessing,可以更加方便的使用,并且主进程会等待子进程结束而结束
首先看一下多进程的使用。
multiprocessing模块就是跨平台版本的多进程模块,提供了⼀个Process类来代表⼀个进程对象,这个对象可以理解为是⼀个独⽴的进程,可以执⾏另外的事情
import multiprocessing
import time
def func():
time.sleep(1)
print("sub process running")
if __name__ = "__main__":
print("main process start")
m = multiprocessing.Process(target=func)
m.start() # 创建进程
print("main process end")
输出:
main process start
main process end
sub process running
在多进程中必须有启动程序if name = “main”:,否则会报 The “freeze_support()” line can be omitted if the program is not going to be frozen to produce an executable.,加上if name = “main”就可以了
以上代码也可以看出,只要实例化一个Process对象,调用start方法就可以启动一个进程,来看一下Process参数:
target:传递函数的引用,新创建的进程执行此函数内的代码
args:给target指定的函数传递参数,并且是以元组的形式传递,如果只有一个参数,元组内记得加逗号
kwargs:给target指定的函数传递参数,是以字典的形式传递
常用方法:
start():启动进程
is_alive():判断子进程是否还在运行
join(timeout):让主进程卡在这等待子进程运行结束,如果有超时时间,表示主进程等待子进程timeout时间再继续向下执行
terminate():立即终止子进程,不管有没有运行结束
常用属性:
name:当前进程的别名,默认为Process-N,N默认是从1开始的整数
pid:当前进程运行的进程号
daemon:设置当前进程是否为守护进程,默认为Fales,不设置守护进程,即主进程等待子进程结束,与多线程的setDaemon()方法相同
import multiprocessing
import time
import os
def func(name, age, **kwargs):
for i in range(10):
print("name is {}".format(name))
print("age is {}".formag(age))
print(kwargs)
time.sleep(0.3)
if __name__ = "__main__":
m = multiprocessing.Process(target=func, args=("xiaogu", 18), kwargs={"n":20}) # 传参
m.start() # 启动进程
print(m.pid) # 子进程进程号
print(m.name) # 子进程别名
time.sleep(1)
m.terminate() # 1秒以后立即终止子进程
m.join()
print("main process finished")
输出:
464
Process-1
name is xiaogu
age is 18
{'n': 20}
name is xiaogu
age is 18
{'n': 20}
name is xiaogu
age is 18
{'n': 20}
main process finished
进程间不共享全局变量
进程是操作系统资源分配的最小单位,进程有自己的内存单元,可以理解为每个进程都复制了一份代码单独执行,相比于线程来说有利于资源的管理,但是开销大。
import multiprocessing
import time
def func1(num):
for i in range(3):
num.append(i)
print("func1 num is {}".format(num))
def func2(num):
print("func2 num is {}".format(num))
if __name__ = "__main__":
num = [1,2]
m1 = multiprocessing.Process(target=func1, args=(num, ))
m1.start()
m1.join()
m2 = multiprocessing.Process(target=func2, args=(num, ))
m2.start()
m2.join()
输出:
func1 num is [1, 2, 0]
func1 num is [1, 2, 0, 1]
func1 num is [1, 2, 0, 1, 2]
func2 num is [1, 2]
从以上结果可以看出,m2进程并没有和m1进程共享全局变量
python的多线程由于GIL的存在,实际上是一种伪多线程,在遇到I/O操作时会自动切换到下一线程,所以在同一时刻只有一个线程在运行,并没有真正的发挥CPU多核能力,而multiprocessing模块充分利用了多核CPU。所以多线程适用于I/O密集型操作,比如网络操作 、磁盘读写操作等,而多进程适用于CPU(计算)密集型操作。举个例子说明多线程在CPU密集型的失败典型
import threading
import multiprocessing
import time
def func():
n = 0
for i in range(100000000): # 循环相加1亿次
n += 1
# 单线程
start_time = time.time()
for i in range(2):
t = threading.Thread(target=func)
t.start()
t.join() # 使用一个线程顺序跑两遍
end_time = time.time()
print(end_time-start_time)
# 多线程
start_time = time.time()
for i in range(2):
t = threading.Thread(target=func)
t.start()
t.join() # 使用两个线程
end_time = time.time()
print(end_time-start_time)
# 多进程
if __name__ = "__main__":
start_time = time.time()
for i in range(2):
m = multiprocessing.Process(target=func)
m.start() # 使用两个进程
t.join()
end_time = time.time()
print(end_time-start_time)
输出:
单线程:23.618102073669434
多线程:52.09776592254639
多进程:11.174023151397705
以上结果可以看出在CPU密集型的执行过程中,多线程的效率明显低于单线程,而多进程的计算时间为单线程的一半,所以多进程适用于CPU密集型,也就是说multiprocessing绕过GIL,发挥了多核CPU的性能。
进程同步锁
多进程虽然不共享全局变量,但是共享文件系统,所以在多个进程同时往同一个文件写入数据时,可能会造成数据混乱,最简单的解决办法就是加锁。。。
import multiprocessing
def func(num, lock):
# lock.acquire()
print("func num is {}".format(num))
time.sleep(0.1)
print("func num is {}".format(num))
print("func num is {}".format(num))
# lock.release()
if __name__ == '__main__':
print("main process start")
lock = multiprocessing.Lock()
for i in range(3):
m = multiprocessing.Process(target=func, args=(i,lock))
m.start()
m.join()
print("main process finished")
不加锁的输出:
main process start
func num is 1
func num is 0
func num is 2
func num is 0
func num is 1
func num is 0
func num is 1
func num is 2
func num is 2
main process finished
加锁的输出:
main process start
func num is 0
func num is 0
func num is 0
func num is 1
func num is 1
func num is 1
func num is 2
func num is 2
func num is 2
main process finished
在不加同步锁时,打印出来的顺序混乱,不利于数据的分析,加上同步锁以后,保证每次获取锁之后都能保证把锁住的代码从头到尾的执行完毕,保证整个数据的完整性,但是由于锁的原因,效率也随之下降。
进程间通信
进程之间可以采用消息队列和管道进行通信,在此只以消息队列为例看一个demo
import multiprocessing
def func1(queue):
for i in range(5):
queue.put("xiaogu num {}".format(i)) # 向队列中放入5条消息
def func2(queue):
while True:
if not queue.empty():
print(queue.qsize())
print(queue.get()) # 设置成死循环一直往外取
else:
break
if __name__ = "__main__":
queue = multiprocessing.Queue() # 没有指定消息的最大数量,则默认为无限大,直到内存尽头
m1 = multiprocessing.Process(target=func1, args=(queue, ))
m2 = multiprocessing.Process(target=func2, args=(queue, ))
m1.start()
m2.start()
m1.join()
m2.join()
输出:
5
xiaogu num 0
4
xiaogu num 1
3
xiaogu num 2
2
xiaogu num 3
1
xiaogu num 4
以上代码实现一个进程向队列中放数据,一个进程从队列中取数据输出到终端,两个进程之间实现通信。
消息队列的常用方法:
Queue.qsize():当前队列中的消息数量
Queue.full():判断队列是否已满,如果是返回True,不是返回False
Queue.empty():判断队列是否已空,如果是返回True,不是返回False
Queue.put():往队列中放消息
Queue.get(block[, timeout]):从队列中往外取消息,block默认为True,即在队列为空的时候阻塞,如果block设置为False,在队列为空时会抛Queue.Empty()异常,如果设置timeout,在队列为空以后的timeout时间内队列中仍没有消息进入,依然会抛Queue.Empty()异常,Queue.put()与此正好相反,但是用法都是一样的。
使用管道(Pipe)实现进程间通信时只能实现两个进程之间的通信,而消息队列可以实现多个进程之间通信,并且管道是全双工的,可以同时收发消息。。。
进程池
如果需要同时开启多个进程,则可以使用进程池,初始化Pool时,可以指定⼀个最⼤进程数,当有新的请求提交到Pool中时, 如果池还没有满,那么就会创建⼀个新的进程⽤来执⾏该请求;但如果池中 的进程数已经达到指定的最⼤值,那么该请求就会等待,直到池中有进程结 束,才会⽤之前的进程来执⾏新的任务
import multiprocessing
import time
def func(n):
print("func {} start".format(n))
time.sleep(0.5)
print("func {} finished".format(n))
if __name__ = "__main__":
pool = multiprocessing.Pool(2) # 创建一个进程池,池中最大进程数为2
for i in range(5):
pool.apply_aysnic(func, args=(i, )) # 通过异步方式往池中放入进程,如果池中进程数达到上限,则请求等待
pool.close() # 关闭进程池,不再接受新的请求
pool.join() # 主进程等待,必须放在close之后
输出:
func 0 start
func 1 start
func 0 finished
func 1 finished
func 2 start
func 3 start
func 3 finished
func 2 finished
func 4 start
func 4 finished