Python日积月累_5_多进程/文件夹拷贝器

多进程

1. 回顾多线程

多线程比较轻量,通过threading.Thread类管理线程,通过start方法开始线程;通过threading.Lock方法创建互斥锁避免资源冲突,通过acquirerelease管理互斥锁;此外还有守护线程等特殊线程。

2. 多进程的创建和维护

多进程是实现多任务的另一条途径,首先,进程是指 具备资源调配能力的程序,是操作系统分配资源的基本单位,进程根据是否正在执行可分为不同状态,例如 新建、就绪、运行、等待/堵塞和死亡。

示例代码:

import multiprocessing
import time

def test1():
    for i in range(5):
        print("1--------")
        time.sleep(1)
    
def test2():
    for i in range(5):
        print("2---------")
        time.sleep(1)

def main():
    p1 = multiprocessing.Process(target=test1)
    p2 = multiprocessing.Process(target=test2)
    p1.start()
    p2.start()

if __name__=="__main__":
    main()

3. 多进程的资源管理:写时拷贝

上面的例子中,主进程会打开两个子进程,代码是共享的,内存的使用遵从写时拷贝的原则,可见,进程完成多任务所耗费的资源很大。

在 Linux 系统中,调用 fork 系统调用创建子进程时,并不会把父进程所有占用的内存页复制一份,而是与父进程共用相同的内存页,而当子进程或者父进程对内存页进行修改时才会进行复制 —— 这就是著名的 写时拷贝 机制。
写时拷贝的原理是基于“引用计数”的概念,通过维护四个字节来存储引用计数,STL的string的写时拷贝就是通过维护一个指针实现的。

Ubuntu中通过ps查看进程信息

在ubuntu中,通过ps -lps --forest查看当前用户的执行进程,通过ps -aux查看全部进程。

“ps -aux” 和 "ps aux"的区别
Note that “ps -aux” is distinct from “ps aux”. The POSIX and UNIX standards require that “ps -aux” print all processes owned by a user named “x”, as well as printing all processes that would be selected by the -a option.
If the user named “x” does not exist, this ps may interpret the command as “ps aux” instead and print a warning. This behavior is intended to aid in transitioning old scripts and habits. It is fragile, subject to change, and thus should not be relied upon.

4. 多进程和多线程的区别

形象的讲,进程可以理解为工厂中的流水线,线程可以理解为流水线上的工人。

一个进程中至少有一个线程,因为线程是 执行/调度资源的单位,进程是 分配资源的基本单位。

5. 进程间的通信_队列(Queue)

如果进程之间需要协同,则需要进行通信,可以是socket、文件,不过更多还是使用 队列(Queue)

示例代码

from  multiprocessing import Queue

q = Queue(3) # 最多同时接收三条数据,缺省时为内存上限
q.push("111") # 放数据
q.get() # 取数据,如果没有剩余数据,则会进入阻塞状态
q.get_nowait() # 不等待的取数据,如果没有数据则报错
q.empty() # 返回bool值,判断是否为空

通过Queue可以实现功能解耦,例如进程A用来下载数据,进程B用来处理数据,通过Queue实现通信:

import multiprocessing

def download_from_web(q):
    """模拟从网上下载数据"""
    data = [11,22,33,44]
    # 向队列中写入数据
    for temp in data:
        q.put(temp)
    print("---下载器已经下载完数据,并存放入队列----")

def analysis_data(q):
    """处理数据"""
    waitting_analysis_data = list()
    while True:
        data = q.get()
        waitting_analysis_data.append(data)
        # 注意q只能在本地电脑创建,而redis可以在分布式的情况下建立进程间通信的队列
        if q.empty():
            print("---处理器已经处理完毕----")
            break

def main():
    # 创建一个队列,缺省为内存的上限
    q = multiprocessing.Queue()
    # 创建多线程,将队列的引用作为实参传递到里面
    p1 = multiprocessing.Process(target=download_from_web,args=(q,))
    p2 = multiprocessing.Process(target=analysis_data,args=(q,))

    p1.start()
    p2.start()

if __name__ == "__main__":
    main()

分布式场景下,一般使用redis处理进程间的通信。

进程池

如果进程数量成百上千时,手动管理进程十分麻烦,可以使用multiprocessing.Pool管理进程。

初始化Pool时,指定最大进程数,当有新的进程时,使用Pool().apply_async(target,args)添加进程,如果Pool还没有满,则会分配进程,否则等待分配。

示例代码:

from multiprocessing import Pool
import time

def worker(i):
    print("进程%d"%i)
    time.sleep(1)    

po = Pool(3) # 进程数最大为3,调用时才会分配资源

for i in range(10):
    # 当进程池满了后继续添加子进程,po仍然会接收,不会报错
    # Pool().apply_async(要调用的目标,(传递给调用目标的参数元组))
    po.apply_async(worker,(i,))

po.close()  # 关闭进程池,关闭po之后不再接收新的请求
po.join()  # 等待po中所有子进程执行完毕,必须放在close语句之后
# 否则主进程结束后,子进程也会关闭,这和线程十分不同。

多进程应用:文件夹拷贝器

第一版:实现基本功能

import os,multiprocessing

def copy_file(old_folder_name,file_name,new_folder_name):
    """完成文件的复制"""
    with open(old_folder_name+'/'+file_name) as f:
        content = f.read()
        with open(new_folder_name+'/'+file_name,'w') as f_new:
            f_new.write(content)

def main():
    # 1. 获取用户要copy的文件夹的名字
    old_folder_name = input("请输入要copy的文件名: ")
    # 2. 创建一个新的文件
    new_folder_name = old_folder_name+"[复件]"
    try:
        os.mkdir(new_folder_name)
    except:
        pas
    # 3. 获取文件夹的所有待copy的文件夹的名字
    file_names = os.listdir(old_folder_name)
    print(file_names)
    # 4. 创建进程池
    po = multiprocessing.Pool(10)
    # 4. 进程池里,子进程复制源文件夹中的文件,到新文件夹下
    for file_name in file_names:
        # 注意:进程池中不会显示异常
        po.apply_async(copy_file,args=(old_folder_name,file_name,new_folder_name))
    
    po.close()
    po.join()
           
if __name__ == "__main__":
    main()

第二版:增加进度条

需求:在主进程中加入显示进度条的功能。
分析:这需要得知哪些进程离开了进程池,要使用multiprocessing.Manager().Process()创建一个队列,每当进程结束,就往这个队列中加入消息,然后主进程接收消息。

import os,multiprocessing

def copy_file(q,old_folder_name,file_name,new_folder_name):
    """完成文件的复制"""
    with open(old_folder_name+'/'+file_name) as f:
        content = f.read()
        with open(new_folder_name+'/'+file_name,'w') as f_new:
            f_new.write(content)
    # 拷贝完成,向队列中放入 已经拷贝完毕 的消息
    q.put(file_name)

def main():
    # 1. 获取用户要copy的文件夹的名字
    old_folder_name = input("请输入要copy的文件名: ")
    # 2. 创建一个新的文件
    new_folder_name = old_folder_name+"[复件]"
    try:
        os.mkdir(new_folder_name)
    except:
        pass

    # 3. 获取文件夹的所有待copy的文件夹的名字
    file_names = os.listdir(old_folder_name)
    print(file_names)
    # 4. 创建进程池
    po = multiprocessing.Pool(10)
    # 创建队列
    q = multiprocessing.Manager().Queue()

    # 4. 进程池里,子进程复制源文件夹中的文件,到新文件夹下
    for file_name in file_names:
        # 注意:进程池中不会显示异常
        po.apply_async(copy_file,args=(q,old_folder_name,file_name,new_folder_name))
    
    po.close()
    # po.join()
    # 主进程 显示进度条,所以去掉po.join()
    all_file_count = len(file_names)
    processed_file_count = 0
    while True:
        file_name = q.get()  # 阻塞
        processed_file_count += 1
        print("已经完成%s的拷贝"%file_name)
        print("拷贝进度:%.2f%%" % (processed_file_count/all_file_count*100))
        if q.empty():
            break

if __name__ == "__main__":
    main()

Reference
[1] 写时拷贝的原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值