进程和线程
- 线程是计算机可以被cpu调度的最小单元;
- 进程是计算机资源分配的最小单元;
- 进程为线程提供资源;
多进程开发
- 进程是计算机中资源分配的最小单元;
- 一个进程可以有有多个线程;
- 同一个进程中的线程共享资源;
- 进程与进程之间是相互隔离的;
- python可以通过多进程利用CPU多核优势,计算密集型操作可以使用多进程提高程序运行效率。
创建多进程
方法一: multiprocessing.Process() 创建多进程
multiprocessing是Python的一个标准模块,它提供了一组用于处理进程的工具。这个模块可以很方便地创建和管理进程。
使用内置模块 multiprocessing.Process() 创建多进程,它接收两个参数:
- 进程函数名,注意不要传入调用。
- 进程函数的参数,以元组的形式传入,若不需要参数可以不指定。
import multiprocessing
def run_task():
print('hi python')
if __name__ == '__main__':
p = multiprocessing.Process(target=run_task)
p.start()
import time
import os
import multiprocessing
def run(name):
print(f'子进程 {name} 开始执行, pid:{os.getpid()}, 父进程 pid:{os.getppid()}')
time.sleep(2)
print(f'子进程 {name} 执行结束')
if __name__ == '__main__':
print(f'主进程开始执行, pid:{os.getpid()}')
process1 = multiprocessing.Process(target=run, args=('process1',)) # 创建子进程1
process2 = multiprocessing.Process(target=run, args=('process2',)) # 创建子进程2
process1.start() # 启动子进程1
process2.start() # 启动子进程2
process1.join() # 等待子进程1执行完毕
process2.join() # 等待子进程2执行完毕
print(f'主进程执行结束, pid:{os.getpid()}')
- Process.start() 启动子进程
- Process.join() 等待子进程结束(堵塞等待)
- Process.is_alive() 判断子进程是否在运行
- Process.close() 结束子进程
关于进程对象常用的一些方法:
# 创建一个进程
p = multiprocessing.Process(target=func, args=(param,))
# 启动进程
p.start()# 当前进程准备就绪,等待被CPU调用(工作单元其实是进程中的线程)
# 阻塞主进程,等待子进程执行完毕之后才会执行主进程后面的代码
#1.程序执行时,程序本身就是一个进程,叫主进程
#2.手动创建的进程叫子进程
#3.主进程的执行中不会等待子进程执行完毕,就会直接执行后面的代码
p.join() # 等待当前进程的任务执行完毕再向下继续执行
# 将进程设置为守护进程(当主进程退出之后,会直接关闭守护进程)
p.daemon = True # 设置为守护进程,主进程执行完毕后,子进程也自动关闭
p.daemon = False # 设置为非守护进程,主进程等待子进程,子进程执行完毕后,主进程才结束
# 判断进程是否在执行状态,返回 bool
t.is_alive()
# 设置进程名
t.name = 'test_process'
# 获取当前进程编号
os.getpid()
# 获取当前进程的父进程编号
os.getppid()
# cpu的个数, 程序一般创建多个进程(cpu多核优势)
multiprocessing.cpu_count()
方法二:进程池
进程池:
-
可以提供指定数量的进程给用户使用;
-
即当有新的请求提交到进程池中时,如果池未满,则会创建一个新的进程用来执行该请求;反之,如果池中的进程数已经达到规定最大值,那么该请求就会等待;
-
只要池中有进程空闲下来,该请求就能得到执行。
-
.提高效率,节省开辟进程和开辟内存空间的时间及销毁进程的时间
-
节省内存空间
Pool.apply() 启动任务(堵塞等待任务完成)
Pool.map() 批量启动任务(堵塞等待任务完成)
Pool.apply_async() 启动任务(非堵塞)
Pool.map_async() 批量启动任务(非堵塞)
from multiprocessing.pool import Pool
from multiprocessing import Process
import time
import os
def run_task(name):
print("主进程的id{}".format(os.getppid()))
print("子进程{}的id:{}".format(name, os.getpid()))
time.sleep(3)
if __name__ == '__main__':
pool = Pool(4)
for i in range(4):
name = f"pool_{i}"
pool.apply_async(run_task, args=(name,))
pool.close()
pool.join()
print("执行完毕")
方法三:自定义进程类
通过自定义类来创建多进程,对于这个类有三点要求:
- 必须继承 multiprocessing.Process 这个父类
- 必须重写 run 方法,这个 run 方法里面就是写业务逻辑,在进程 start() 后将会被调用
- 如果有构造函数,必须重载父类的 init 方法
import time
import os
import multiprocessing
class MyProcess(multiprocessing.Process):
def __init__(self, name):
# 这里一定要调用父类的初始化函数,否则无法创建进程
super().__init__()
self.name = name
def run(self):
print(f'子进程 {self.name} 开始执行, pid:{os.getpid()}, 父进程 pid:{os.getppid()}')
time.sleep(2)
print(f'子进程 {self.name} 执行结束')
if __name__ == '__main__':
print(f'主进程开始执行, pid:{os.getpid()}')
process1 = MyProcess('process1') # 创建子进程1
process2 = MyProcess('process2') # 创建子进程2
process1.start() # 启动子进程1
process2.start() # 启动子进程2
process1.join() # 等待子进程1执行完毕
process2.join() # 等待子进程2执行完毕
print(f'主进程执行结束, pid:{os.getpid()}')
进程之间数据共享
- 进程是资源分配的最小单元,每个进程都维护自己独立的数据,不共享。
import multiprocessing
def task(data):
data.append(0)
print("子进程", data)
if __name__ == "__main__":
data_list = list()
p = multiprocessing.Process(target=task, args=(data_list,))
p.start()
p.join()
print("主进程:", data_list)
进程之间的数据共享
进程间通信不能使用线程间通信那种全局变量的方式,因为进程间的数据是相互隔离的,进程间通信一般有三种方式。
使用 multiprocessing.Queue()
import time
from multiprocessing import Process, Queue
def producer(queue):
print('producer producing goods start')
for i in range(1, 6):
queue.put(f'good_{i}')
print('producer producing goods end')
def consumer(queue):
goods_name = queue.get()
print(f'consumer consuming goods: {goods_name} start')
time.sleep(1)
print(f'consumer consuming goods: {goods_name} end')
if __name__ == "__main__":
queue = Queue(10)
producer_process = Process(target=producer, args=(queue,))
producer_process.start()
time.sleep(1) # sleep 1s 确保生产者先将数据写入队列
while not queue.empty():
consumer_process = Process(target=consumer, args=(queue,))
consumer_process.start()
python 中一共有三个 Queue()
- queue.Queue()
- multiprocessing.Queue()
- multiprocessing.Manager().Queue()
要注意进程间通信使用的是第二个,第一个只能用于线程间通信,而如果是使用进程池创建的进程,则只能用第三个。
pipe()
多个进程间通信可以使用 Queue,而如果只有两个进程,可以使用 Pipe,它只允许一个存一个取,但是它的性能是高于 Queue 的。
from multiprocessing import Process, Pipe
def producer(pipe):
print('producer producing goods start')
pipe.send(f'good_1')
print('producer producing goods end')
def consumer(pipe):
goods_name = pipe.recv()
print(f'consumer consuming goods: {goods_name} start')
time.sleep(1)
print(f'consumer consuming goods: {goods_name} end')
if __name__ == "__main__":
print('----主进程开始----')
s_pipe, r_pipe, = Pipe()
producer_process = Process(target=producer, args=(s_pipe,))
consumer_process = Process(target=consumer, args=(r_pipe,))
producer_process.start()
consumer_process.start()
producer_process.join()
consumer_process.join()
print('----主进程结束----')
使用 Manager() 共享内存
Manager() 可以创建一个专门用来维护进程间数据共享的进程,它能提供 python 所支持的任何数据结构,也能用来实现进程间的数据通信。
import time
from multiprocessing import Process, Manager
def producer(m_list):
print('producer producing goods start')
for i in range(1, 6):
m_list.append(f'good_{i}')
print('producer producing goods end')
def consumer(m_list):
while m_list:
goods_name = m_list.pop()
print(f'consumer consuming goods: {goods_name} start')
time.sleep(1)
print(f'consumer consuming goods: {goods_name} end')
if __name__ == "__main__":
print('----主进程开始----')
m_list = Manager().list()
producer_process = Process(target=producer, args=(m_list,))
consumer_process = Process(target=consumer, args=(m_list,))
producer_process.start()
consumer_process.start()
producer_process.join()
consumer_process.join()
print('----主进程结束----')
共享内存的原理:多个进程的虚拟内存会映射到同一块物理内存,多个进程可以共享这一块物理内存的数据,所以多进程可以通过共享内存来共享数据。
但是这里需要注意的是,系统内核不负责多进程对共享内存中数据的修改和访问进行同步,意思是,如果多个进程并发的修改共享内存中的同一个数据,很可能导致数据被错误修改,出现数据不一致的情况。
所以多个进程如果同时操作共享内存中的同一个资源时,需要加锁,且这里加的锁必须是 Manager 共享对象中的锁。
from multiprocessing import Manager,Process
def task1(num, lock):
for i in range(10000):
with lock:
num.value += 1
def task2(num, lock):
for i in range(10000):
with lock:
num.value -= 1
if __name__ == "__main__":
manager = Manager() # 定义一个共享内存对象
num = manager.Value("jaye", 7)
lock = manager.Lock() # 获取锁
p1 = Process(target=task1, args=(num, lock))
p2 = Process(target=task2, args=(num, lock))
p1.start()
p2.start()
p1.join()
p2.join()
print(f"主进程的num:{num.value}")