3.3进程

3.3.1进程概述

通俗理解一个运行起来的程序或者软件叫做进程

(1)每次启动一个进程都需要向操作系统索要运行资源(内存),进程是操作系统资源分配的基本单位

(2)进程只提供运行资源,真正干活的是线程,线程是执行程序中对应的代码的, 默认一个进程默认只提供一个线程(主线程),当然还可以在一个进程里面开辟多个线程

(3) 如何理解进程:把公司类比成进程,公司会给我们提供办公资源(办公桌椅,办公电脑等),公司里面的员工利用公司里面的资源去干活,所以公司可以想成进程,员工就是线程

3.3.2进程和线程的对比

(1)进程是操作系统资源分配的基本单位,线程是cpu调度的基本单位,cpu调度哪个线程,哪个线程去执行对应的代码

(2)进程之间不共享全局变量, 线程之间共享全局变量,但是要注意资源竞争的问题,解决问题的方式是互斥锁或者线程同步

(3)多进程开发比单进程多线程开发代码的稳定性要强,但是多进程开发比多线程开发资源开销要多

(4)多进程开发某一个进程挂掉不会影响其它进程的运行,单进程开发该进程挂掉所有的线程都死掉了, 多线程开发会共享进程中资源

(5)先有进程,线程是依附在进程里面的,默认一个进程会提供一个线程(主线程)

3.3.3创建进程

import multiprocessing
sub_process = multiprocessing.Process(group=None, target=show_info, name="myprocess", args=("杨钰莹", 18))
sub_process.start()

参数说明:

(1)group: 进程组, 默认值必须是None

(2)target:执行的目标函数

(3)name: 进程名称, 如果不指定进程名字的格式: Process-1,....

(4)args: 以元组的方式传参

(5)kwargs:以字典的方式传参

3.3.4获取进程、进程编号

import  multiprocessing 
import os
def work():
    # 获取当前进程
    current_process = multiprocessing.current_process()
    print("work:", current_process)
    # 扩展-获取进程编号
    print("work的进程编号:", current_process.pid, os.getpid())
    # 获取父进程的编号
    print("work进程的父进程编号:", os.getppid())
    for i in range(10):
        print("工作中....")
        time.sleep(0.2)
        # 扩展: 根据进程编号杀死指定进程
        os.kill(os.getpid(), 9)

3.3.5进程注意点

(1)进程之间不共享全局变量

(2)主进程等待子进程完成再退出

若不想主进程一直等待,可用:

①守护主进程

sub_process = multiprocessing.Process(target=test)
sub_process.daemon = True
time.sleep(1)
sub_process.start()

②销毁子进程

sub_process = multiprocessing.Process(target=test)
time.sleep(1)
sub_process.terminate()
sub_process.start()

3.3.6进程间通信——Queue

可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息队列(先进先出)程序

创建消息队列:

queue = multiprocessing.Queue()

说明:

(1)初始化Queue()对象时(例如:queue=Queue()),若括号中没有指

定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数

量没有上限(直到内存的尽头)

(2)放入数据:

queue.put(xxxx)

注意:消息队列可以放入任何数据类型。如果队列满了使用put放入

数据会等待,直到队列有空闲位置才能放入数据。如果队列满了使用

put_nowait不会等待,放入数据不成功会崩溃。 为了安全起见放入数

据统一使用put

(3)获取队列里的数据

value = queue.get()
print(value)

注意: 如果队列空了,那么使用get取值会等待,直到队列有数据才

能获取数据。如果队列空了, 使用get_nowait不会等待,如果取值不

成功则崩溃。为了安全起见获取数据使用get。

(4)获取队列的个数,注意:mac不能使用此方法

queue_size = queue.qsize()

(5)判断队列是否为空

queue.empty()——不靠谱

如果队列为空,返回True,反之False

注意:判断队列是否为空的时候,不会等数据全部写入到队列以

后再判断,有可能出现获取的队列是空的

解决办法:保证数据全部写入到队列里面以后再去做判断

① 延时

time.sleep(0.001)

②根据个数判断

if queue.qsize() == 0:
print("队列为空")
else:
print("队列不为空")

3.3.7进程池

池子里面放的都是进程,进程的创建是进程池根据执行任务的情况自动创建进程,进程池会合理利用现有的进程完成多任务

import multiprocessing
import time


# 拷贝任务
def copy_work():
    print("复制中....,", multiprocessing.current_process().pid)
    # 默认进程池创建的进程是守护主进程,那么主进程退出进程池中的子进程就直接销毁
    time.sleep(1)



if __name__ == '__main__':
    # 创建进程池, 3:表示进程池中最大进程的个数
    pool = multiprocessing.Pool(3)
    # 使用进程池执行对应任务,默认大批量的复制任务让进程池帮我们完成
    for i in range(10):
        # 同步执行对应的任务,是一个任务执行完成另外一个任务才能执行
        # pool.apply(copy_work)
        # 异步执行,多个任务一起执行,任务之间不会等待
        pool.apply_async(copy_work)
    #注意:异步执行要有下面的操作:关闭进程池和等待
    # 关闭进程池,表示进程池以后不再接收对应的任务了,主进程会根据现有进程池的任务执行完成以后程序就可以退出了
    pool.close()
    # 主进程等待进程池把任务执行完成以后程序再退出
    pool.join()

    # 提醒: 进程池会根据任务执行情况自动创建进程,进程池会尽量少创建进程,可以利用现有的进程完成多任务

3.3.8进程池中的Queue

(1)如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue()

(2)创建进程池中queue

queue = multiprocessing.Manager().Queue(3)

(3)使用同步的方式执行任务

# 参数要使用元组传参

pool.apply(write_data, (queue, ))

(4)使用异步的方式去执行任务 ,异步执行任务会返回一个ApplyResult 对象

result = pool.apply_async(write_data, (queue,))
print(result)

(5)扩展:主进程等待异步任务执行完成以后代码再继续往下执行

result.w·ait()
pool.apply_async(read_data, (queue,))
# 异步执行任务,主进程需要等待进程池执行完成以后程序再退出
pool.close()
pool.join()

3.3.9案例:文件夹拷贝器

import os
import shutil
import multiprocessing
import time


# 拷贝文件的任务
def copy_work(src_dir, dst_dir, file_name):
    print(multiprocessing.current_process().pid)
    # 源文件的路径
    src_file_path = src_dir + "/" + file_name
    # 目标文件的路径
    dst_file_path = dst_dir + "/" + file_name
    # 打开目标文件
    with open(dst_file_path, "wb") as dst_file:
        # 打开源文件获取源文件的数据把数据写入到目标文件里面
        with open(src_file_path, "rb") as src_file:
            while True:
                # 读取数据
                src_file_data = src_file.read(1024)
                if src_file_data:
                    # 把源文件的数据写入到目标文件里面
                    dst_file.write(src_file_data)
                else:
                    break
    time.sleep(0.5)
if __name__ == '__main__':
    # 源目录
    src_dir = "test"
    # 目标目录
    dst_dir = "/home/python/Desktop/test"

    # 判断文件夹是否存在
    if os.path.exists(dst_dir):
        # 删除目标目录
        shutil.rmtree(dst_dir)
    # 创建文件夹
    os.mkdir(dst_dir)
    # 获取源目录的文件列表
    file_name_list = os.listdir(src_dir)

    # 创建进程池
    pool = multiprocessing.Pool(3)

    # 遍历文件列表获取对应的文件名
    for file_name in file_name_list:
        # 使用进程池完成多任务的文件拷贝
        pool.apply_async(copy_work, (src_dir, dst_dir, file_name))
    # 关闭进程池
    pool.close()
    # 主进程等待进程池执行完成以后程序再退出
    pool.join()