首页 > 解决方案 > 使用线程读取文件

问题描述

我尝试编写一个 python 程序,使用 python 的套接字将文件从一台 PC 发送到另一台 PC。但是当文件大小增加时,它需要很多时间。是否可以使用线程顺序读取文件的行?

我认为的概念如下:每个线程分别并顺序地从文件中读取行并通过套接字发送。有可能吗?或者你有什么建议吗?

标签: pythonsockets

解决方案


首先,如果您想在不使用线程的情况下尽可能加快速度,那么一次读取和发送一行可能会非常慢。Python 在缓冲文件方面做得很好,一次给你一行供你阅读,但是你正在通过网络发送微小的 72 字节数据包。您希望尽可能一次发送至少 1.5KB。

理想情况下,您想使用该sendfile方法。Python 将告诉操作系统以最有效的方式通过套接字发送整个文件,而完全不涉及您的代码。不幸的是,这在 Windows 上不起作用。如果您关心这一点,您可能希望直接使用本机 API 1pywin32或切换到更高级别的网络库,例如twistedor asyncio


现在,线程呢?

好吧,在不同的线程中一次读取一行并没有太大帮助。线程必须顺序读取,争夺文件对象中的读取指针(和缓冲区),并且它们可能必须顺序写入套接字,您甚至可能需要一个互斥锁来确保它们按顺序写入内容。因此,无论其中哪一个最慢,您的所有线程都将最终等待轮到它们。2


此外,甚至忘记套接字:在现代硬件上的某些情况下,并行读取文件可能会更快,但总的来说它实际上要慢得多。想象一下,该文件位于慢速磁性硬盘驱动器上。一个线程正在尝试读取第一个块,下一个线程正在尝试读取第 64 个块,下一个线程正在尝试读取第 4 个块……这意味着您花费更多时间来回寻找磁盘头而不是实际读取数据。

但是,如果您认为您可能处于并行读取可能会有所帮助的情况之一,您可以尝试一下。这不是微不足道的,但也不是那么难。

首先,您想要对固定大小的块进行二进制读取。您将需要尝试不同的大小——也许 4KB 是最快的,也许是 1MB……所以请确保将其设为常数,您只需在代码中的一个位置轻松更改即可。

接下来,您希望能够尽快发送数据,而不是序列化。这意味着您必须在每个块之前发送某种标识符,例如文件中的偏移量。

该函数将如下所示:

def sendchunk(sock, lock, file, offset):
    with lock:
        sock.send(struct.pack('>Q', offset)
        sent = sock.sendfile(file, offset, CHUNK_SIZE)
        if sent < CHUNK_SIZE:
            raise OopsError(f'Only sent {sent} out of {CHUNK_SIZE} bytes')

...除了(除非您的文件实际上都是 的倍数CHUNK_SIZE)您需要决定要为合法的 EOF 做什么。也许在任何块之前发送总文件大小,并用空字节填充最后一个块,并让接收者截断最后一个块。

然后,接收方可以循环读取 8+CHUNK_SIZE 字节,解包偏移量,查找并写入字节。


1. 看TransmitFile——但为了使用它,你必须知道如何在 Python 级别的socket对象和 Win32级别HANDLE的对象之间进行转换,等等;如果你从来没有这样做过,那就有一个学习曲线——我不知道有什么好的教程可以帮助你入门。

2. 如果你真的很幸运,比如说,文件读取的速度只有套接字写入的两倍,你实际上可能会从流水线中获得 33% 的加速——也就是说,一次只能写入一个线程,但是等待写入的线程大部分已经完成了读取,所以至少你不需要在那里等待。


推荐阅读