python基础——多进程

程序和进程的区别

程序:像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
发布了36 篇原创文章 · 获赞 2 · 访问量 935

猜你喜欢

转载自blog.csdn.net/zzrs_xssh/article/details/104381152