python中的多进程拷贝文本文件2

本文介绍了一个使用Python的多进程技术实现的文件备份系统。该系统通过读取源文件并将其内容复制到新目录,利用进程池提高备份效率。文章详细展示了如何使用multiprocessing模块进行进程间通信,以及如何监控备份进度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码描述:
拷贝的原理:
1). 读取源文件的内容;
2). 写入新的文件中;

import os
import time
import multiprocessing  # 进行进程间的通信, Queue
from queue import Queue
from concurrent.futures import ProcessPoolExecutor


def copyFileTask(oldFolderName, newFolderName, filename, queue):
    """
    import os
    # 拼接生成绝对路径
    os.path.join('/mnt', 'file')
    '/mnt/file'
    os.path.join('/mnt/', 'file')
    '/mnt/file'
    :param oldFolderName: /root/day21/
    :param newFolderName: /root/day21_backup_201901
    :param filename: file1
    :return:
    """
    # 两者相同的效果, with语句执行节航速后, 自动关闭文件对象;
    # with open('/etc/passwd') as f:
    #     pass

    # f = open('/etc/passwd')
    # with f:
    #     pass

    fr = open(os.path.join(oldFolderName, filename), 'rb')
    fw = open(os.path.join(newFolderName, filename), 'wb')
    with fr, fw:
        content = fr.read(1024 * 3)
        while content:
            fw.write(content)
        queue.put(filename)
        # print(queue.qsize())


def main():
    # 判断备份目录是否存在
    while True:
        oldFolderName = input("请输入备份的目录名:")
        # oldFolderName = "/var/log/"
        if os.path.exists(oldFolderName):
            break
        else:
            print("%s目录不存在" % (oldFolderName))

    dateName = time.strftime('_%Y_%m_%d_%H_%M')  # '2019_01_20'
    newFolderName = oldFolderName + '_备份' + dateName
    if os.path.exists(newFolderName):
        # os.rmdir(newFolderName)  # 删除空目录
        # os.removedirs(newFolderName)
        os.system('rm -fr %s' %(newFolderName))
    # 新建备份的目录;
    os.mkdir(newFolderName)
    print("正在创建备份目录%s....." % (newFolderName))
    # 获取备份目录中的所有文件名;
    fileNames = os.listdir(oldFolderName)

    # 队列, 存储已经备份的文件;
    # ****如果是用进程池,那么就需要使用Manager().Queue()队列才能在各子进程间通信,否则沒用
    queue = multiprocessing.Manager().Queue()
    # queue = Queue()

    # pool = multiprocessing.Pool(4)
    pool = ProcessPoolExecutor(4)

    for name in fileNames:
        # 给进程池分配任务
        # pool.apply_async(copyFileTask, args=(oldFolderName,
        #                                      newFolderName,
        #                                      name,
        #                                      queue))

        pool.submit(copyFileTask, oldFolderName, newFolderName, name, queue)

    # 100个文件, 1个文件   1%

    num = 0  # 当前备份的文件数
    allNum = len(fileNames)  # 总备份的文件数
    # print(num, allNum)
    while num < allNum:
        # print(queue.qsize())
        queue.get()
        num += 1
        copyRate = num / allNum  # 0.2322
        # \r使得光标不换行;
        print("\r\r备份的进度为%.2f%%" % (copyRate * 100), end='')

    print("备份成功;")


if __name__ == '__main__':
    main()
<think>为了并行处理大文本文件,我们可以采用多种方法。主要思路是将大文件分割成多个块,然后使用多个进程或线程同时处理这些块。需要注意的是,由于文本文件的行是可变长度的,直接按字节分割可能会导致行被截断。因此,我们需要确保每个块的边界是行边界。 以下是几种并行处理大文本文件的方法: 1. **使用多进程和文件分块**:将文件分割成多个块,每个块由一个进程处理。每个块的开始和结束需要调整到行边界。 2. **使用多进程和任务队列**:主进程读取文件,将行分发给工作进程(生产者-消费者模式)。 3. **使用线程池处理行**:对于I/O密集型任务,使用线程池逐行处理(但Python的GIL可能会限制CPU密集型任务的并行效果)。 4. **使用第三方库**:如`Dask`、`PySpark`等,它们提供了更高级的并行处理接口。 下面,我将重点介绍第一种方法(多进程分块处理)和第二种方法(生产者-消费者模式)。 方法一:多进程分块处理(适用于CPU密集型任务) 步骤: a. 确定文件大小。 b. 将文件分割成大小近似相等的块(每个进程处理一块)。 c. 每个进程读取其对应的块,并调整块的起始位置和结束位置到行边界。 d. 每个进程独立处理自己的块。 注意:这种方法要求处理任务是无状态的,即不依赖于文件其他部分的数据。 方法二:生产者-消费者模式(适用于行间无依赖的处理) 步骤: a. 主进程(生产者)读取文件,将行放入一个队列。 b. 多个工作进程(消费者)从队列中获取行并处理。 这种方法的优点是简单,且能保证行边界,但队列可能成为瓶颈,尤其是行非常短时。 下面给出两种方法的代码示例: 方法一:多进程分块处理 注意:此方法适合处理块内行独立的任务,例如统计每行单词数,然后整体求和。但如果需要按行顺序处理则不适用。 ```python import os import multiprocessing def process_chunk(start, end, file_path, result_queue): """处理文件块""" chunk_data = [] with open(file_path, 'r', encoding='utf-8') as f: # 移动到块的起始位置 f.seek(start) # 调整起始位置:如果不是行首,则跳过第一行(因为可能是被截断的行) if start != 0: f.readline() # 逐行读取直到结束位置 while f.tell() < end: line = f.readline() if not line: break # 处理行(这里用示例函数) processed_line = process_line(line) chunk_data.append(processed_line) # 将本块的结果放入队列(或者根据需求进行其他操作) result_queue.put(chunk_data) def process_line(line): """示例处理函数:返回每行的单词数""" return len(line.split()) def parallel_file_processing(file_path, num_processes=None): """并行处理文件""" if num_processes is None: num_processes = multiprocessing.cpu_count() # 获取文件大小 file_size = os.path.getsize(file_path) chunk_size = file_size // num_processes chunks = [] start = 0 # 创建队列用于收集结果 result_queue = multiprocessing.Queue() processes = [] # 确定每个块的起始和结束位置 for i in range(num_processes): end = start + chunk_size if i == num_processes - 1: end = file_size chunks.append((start, end)) start = end # 创建进程 for start, end in chunks: p = multiprocessing.Process(target=process_chunk, args=(start, end, file_path, result_queue)) processes.append(p) p.start() # 等待所有进程完成 for p in processes: p.join() # 收集结果 results = [] while not result_queue.empty(): results.extend(result_queue.get()) return results # 使用示例 if __name__ == '__main__': # 注意:在Windows上,必须将并行代码放在if __name__ == '__main__'中 result = parallel_file_processing('large_file.txt') print(f"总行数: {len(result)}") print(f"总单词数: {sum(result)}") ``` 方法二:使用进程池和队列(生产者-消费者) 这里我们使用`multiprocessing.Pool`和`Queue`(但注意,`multiprocessing.Queue`在进程池中使用不太方便,我们可以使用`Pool.imap`或`Pool.map`,但需要将文件行先读入内存,这在大文件时不可行)。因此,我们可以使用`Pool.imap_unordered`来处理一个生成器产生的行。 但更好的方式是使用`multiprocessing.Manager`来创建一个共享队列,然后使用进程池中的进程去消费。然而,更简单且高效的方式是使用`Pool.imap`或`imap_unordered`,并逐行读取文件(但读取文件是单线程的)。这样,我们可以将读取和并行处理重叠: 步骤: a. 主进程逐行读取文件(避免内存不足,使用迭代器)。 b. 将行分发给进程池中的进程。 示例代码: ```python from multiprocessing import Pool import time def process_line_wrapper(line): """包装处理函数,注意:这里需要处理换行符等""" return process_line(line.strip()) def parallel_file_processing_pool(file_path, num_processes=None): """使用进程池并行处理文件""" if num_processes is None: num_processes = multiprocessing.cpu_count() pool = Pool(processes=num_processes) results = [] # 逐行读取文件,并使用进程池处理 # 使用imap_unordered(顺序不重要)或者imap(保持顺序) with open(file_path, 'r', encoding='utf-8') as f: # 使用imap_unordered可以更快(不保证顺序) result_iter = pool.imap_unordered(process_line_wrapper, f, chunksize=100) # chunksize可以提高效率 for res in result_iter: results.append(res) pool.close() pool.join() return results # 使用示例 if __name__ == '__main__': result = parallel_file_processing_pool('large_file.txt') print(f"总行数: {len(result)}") print(f"总单词数: {sum(result)}") ``` 注意:方法二中的`chunksize`参数表示每次传递给工作进程的行数(一批),设置合适的`chunksize`可以减少进程间通信的开销。 两种方法的比较: - 方法一(分块): 优点:每个进程独立读取文件的一部分,I/O并行,适合非常大的文件。 缺点:需要处理块边界,代码复杂;如果文件行很长,可能块内行数不均衡。 - 方法二(进程池): 优点:代码简单,无需处理边界问题。 缺点:文件读取是单线程的,可能成为瓶颈;如果处理速度远快于读取速度,则工作进程可能空闲。 选择哪种方法取决于具体场景: - 如果处理是CPU密集型且I/O不是瓶颈,方法二更简单。 - 如果I/O是瓶颈(比如文件在高速存储上),方法一可以利用多个I/O通道(但通常单个文件只能顺序读取,所以方法一在普通磁盘上可能不会提高读取速度,但处理是并行的)。 另外,如果处理有状态依赖(如行与行之间有关联),则并行处理需要特别设计,可能需要将任务拆分成独立单元。 性能优化提示: - 使用SSD可以显著提高文件读取速度。 - 调整块大小(方法一)或`chunksize`(方法二)以平衡负载和通信开销。 - 对于超大型文件,考虑结合使用分块和进程池(将文件分成大块,每个块由一个进程处理,该进程内部再使用多线程或多进程处理)。 注意:在Windows上运行多进程程序时,需要将主要代码放在`if __name__ == '__main__':`中,以避免无限创建子进程。 最后,如果处理任务涉及大量I/O(如网络请求),则可以考虑使用多线程(`ThreadPool`)来避免阻塞。 根据任务类型选择: - CPU密集型:多进程 - I/O密集型:多线程 示例:使用多线程池(只需将`Pool`替换为`ThreadPool`,来自`multiprocessing.pool`或使用`concurrent.futures.ThreadPoolExecutor`) 使用`concurrent.futures`的示例: ```python from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor import multiprocessing def parallel_file_processing_futures(file_path, pool_executor=ProcessPoolExecutor, max_workers=None): """使用concurrent.futures并行处理文件""" if max_workers is None: max_workers = multiprocessing.cpu_count() with pool_executor(max_workers=max_workers) as executor: with open(file_path, 'r', encoding='utf-8') as f: # 提交任务 futures = [executor.submit(process_line_wrapper, line) for line in f] # 或者使用executor.map # results = list(executor.map(process_line_wrapper, f)) # 如果使用submit,则收集结果 results = [f.result() for f in futures] return results ``` 这里,如果使用`ProcessPoolExecutor`,则适用于CPU密集型任务;使用`ThreadPoolExecutor`则适用于I/O密集型任务。 总结:并行处理大文本文件的关键在于分割任务并分配给多个工作单元,同时注意任务之间的独立性和负载均衡。 --- §§相关问题§§: 1. 在Python中,多进程和多线程处理大文件时各有什么优缺点? 2. 如何避免并行处理大文件时出现的内存溢出问题? 3. 如果文本文件的每一行处理都依赖于前几行的结果,如何实现并行? 4. 使用`Dask`库处理大文本文件有哪些优势?</think>### Python 并行处理大文本文件的解决方案 #### 方法 1:使用多进程和队列(推荐用于 CPU 密集型任务) ```python import multiprocessing import os from collections import defaultdict def worker(task_queue, result_queue): """工作进程处理函数""" while True: chunk = task_queue.get() if chunk is None: # 结束信号 break # 处理数据块 local_count = defaultdict(int) for line in chunk: # 示例:统计单词频率 words = line.strip().split() for word in words: local_count[word] += 1 result_queue.put(local_count) def parallel_process_file(file_path, num_processes=None): """主进程管理函数""" if num_processes is None: num_processes = os.cpu_count() or 4 # 创建进程池 task_queue = multiprocessing.Queue() result_queue = multiprocessing.Queue() processes = [] # 启动工作进程 for _ in range(num_processes): p = multiprocessing.Process(target=worker, args=(task_queue, result_queue)) p.start() processes.append(p) # 分发任务(分块读取) chunk_size = 1000 # 每块行数 with open(file_path, 'r', encoding='utf-8') as f: chunk = [] for line in f: chunk.append(line) if len(chunk) >= chunk_size: task_queue.put(chunk) chunk = [] if chunk: # 处理剩余行 task_queue.put(chunk) # 发送结束信号 for _ in range(num_processes): task_queue.put(None) # 收集结果 final_result = defaultdict(int) for _ in range(num_processes): local_count = result_queue.get() for word, count in local_count.items(): final_result[word] += count # 等待进程结束 for p in processes: p.join() return final_result # 使用示例 if __name__ == '__main__': result = parallel_process_file("large_file.txt") print(f"发现 {len(result)} 个唯一单词") ``` #### 方法 2:使用 concurrent.futures(更简洁的 API) ```python from concurrent.futures import ProcessPoolExecutor import os def process_chunk(chunk): """处理文本块""" # 示例:计算行数 return sum(1 for line in chunk if line.strip()) def chunk_generator(file_path, chunk_size=5000): """生成文本块""" with open(file_path, 'r', encoding='utf-8') as f: chunk = [] for line in f: chunk.append(line) if len(chunk) >= chunk_size: yield chunk chunk = [] if chunk: yield chunk def parallel_with_futures(file_path): """使用 ProcessPoolExecutor 并行处理""" total_lines = 0 with ProcessPoolExecutor(max_workers=os.cpu_count()) as executor: # 提交所有任务 futures = [ executor.submit(process_chunk, chunk) for chunk in chunk_generator(file_path) ] # 收集结果 for future in futures: total_lines += future.result() return total_lines # 使用示例 if __name__ == '__main__': line_count = parallel_with_futures("large_file.txt") print(f"文件总行数: {line_count}") ``` #### 方法 3:使用 Dask 库(适合超大规模数据处理) ```python import dask.bag as db def process_line(line): """处理单行数据""" # 示例:提取每行第一个单词 return line.split()[0] if line.strip() else None def process_with_dask(file_path): """使用 Dask 进行分布式处理""" # 创建 Dask bag(延迟执行的集合) bag = db.read_text(file_path, blocksize="64MB") # 应用处理函数 processed = bag.map(process_line) # 执行计算并收集结果 return processed.compute() # 使用示例 if __name__ == '__main__': results = process_with_dask("very_large_file.txt") print(f"处理了 {len(results)} 行数据") ``` ### 关键原理与最佳实践 1. **并行策略选择**: - **I/O 密集型**:使用多线程(`ThreadPoolExecutor`) - **CPU 密集型**:使用多进程(`ProcessPoolExecutor`) - **超大规模数据**:使用 Dask/Ray 分布式框架 2. **数据分块原则**: - 理想块大小:10-100MB(平衡任务分配和通信开销) - 避免小任务:减少进程间通信(IPC)开销 - 行边界处理:确保块在行尾结束 3. **性能瓶颈分析**: ```mermaid graph LR A[磁盘I/O] --> B[数据传输] B --> C[进程间通信] C --> D[CPU处理] D --> E[结果合并] ``` - 优化顺序:I/O > 通信 > CPU > 合并 4. **内存管理技巧**: - 使用生成器避免预加载全部数据 - 结果聚合使用增量更新 - 限制并行任务数量(避免 OOM) 5. **错误处理建议**: ```python try: # 处理代码 except Exception as e: # 记录错误行/块位置 # 返回错误标识而非终止 ``` ### 高级优化技术 1. **内存映射文件加速**: ```python import mmap def process_with_mmap(file_path): """使用内存映射加速I/O""" with open(file_path, "r+") as f: # 创建内存映射 mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) # 寻找行边界 start = 0 while start < len(mm): end = mm.find(b'\n', start) if end == -1: break line = mm[start:end].decode('utf-8') # 处理行 process_line(line) start = end + 1 ``` 2. **混合并行模型**: - 主进程:I/O 读取 - 子进程:CPU 处理 - 子线程:结果写入 3. **零拷贝数据传输**: - 使用共享内存(`multiprocessing.shared_memory`) - 避免大数据在进程间复制 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值