进程和线程

  • 线程是计算机可以被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}") 
07-26 23:34