【问题标题】:how can write code to download in a parallel way?如何编写代码以并行方式下载?
【发布时间】:2012-01-19 02:29:16
【问题描述】:

我想获取一个文件的并行下载,例如,如果文件大小为54 kb,我想以10kb的块为单位下载文件的内容。

另外,我一次不超过 5 个请求。 但如何?想过用fork(),但是不太明白怎么用。

1-10 首次请求
11-20 秒请求
21-30 第三次请求
31-40 第四次请求
41-50第五次请求


51-54 等到过去的一个请求结束。然后它将被执行。

我不关心获取数据的方法(recv 等)。我只想知道如何实现并发方法? (如果我可以使用 fork() 会更好)

【问题讨论】:

  • 我建议您开始寻找有关线程的教程。如果在一个过程中完成所有下载,同步下载可能会更容易。否则,您可能需要查看有关共享内存或共享文件和/或文件锁定的教程。
  • 是否可以在孩子中使用线程?
  • 您通常不会混合使用子进程和线程,而是使用其中之一。多个线程都共享创建它们的进程的资源,而单独的进程不共享任何东西。
  • 你确认我两个进程(通过 2 个 fork 获得)不共享变量或其他数据,对吗?相反,两个线程这样做?
  • 另一种方法是在一个线程中完成所有操作并使用select 查找哪些套接字已接收到数据。

标签: c pthreads fork concurrent-programming


【解决方案1】:

有一些现成的软件库可以提供此功能。我能想到的主要是curl。您可以在here找到curl multi库的简单介绍。

通常最好避免重新发明轮子,除非您有充分的理由(例如改善技术世界或进行学术研究)。

为了学术研究,由于没有“仅链接”的答案就足够了,我将详细说明一种可能的多路复用套接字方法。


非阻塞套接字

第一种也是目前最可移植的方法是使用非阻塞套接字和/或非阻塞套接字调用,但是实现这一点很重要(尤其是当使用非阻塞套接字调用作为反对将O_NONBLOCK 设置为文件描述符):有些东西仍然会阻塞。例如,除非您将文件描述符设置为非阻塞模式,否则您无法让connect 立即返回,当然您getaddrinfo(以及类似的标准名称解析函数)也会阻塞。

当您使用非阻塞文件或调用函数时,函数将立即返回。如果没有准备好数据,他们将通过返回值来表明这一点。如果有数据准备好处理,再次通过返回值显示。

有两种方法(据我所知)可以确保非阻塞套接字调用(包括connect)。

  1. 对于类 Unix 系统,请致电 fcntl(socket_fd, fcntl(socket_fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK)。之后,所有对readwriteacceptconnect 的调用将立即返回,不会有任何延迟。 connect 有一些专门用于此的错误代码(在 errno 中),例如 EALREADYEINPROGRESSEISCONNEWOULDBLOCK,您需要检查它们,因为当您启用非阻塞,有些错误返回值实际上是变相的成功返回值;你需要检查errno
  2. 对于 Windows 系统,请致电 ioctlsocket(socket_fd, FIONBIO, (u_long[]){1})。除了errno 代码不会是errno 代码(相反,它们将是GetLastError() 代码)并且它们是......可能不同的值,我不知道之外,将出现相同的语义。不知道。但是,它们中的许多名称相似,因此在我的项目中,我通常使用以下名称:

    #ifdef _WIN32
    #define set_nonblock(fd) (ioctlsocket(fd, FIONBIO, (u_long[]){1}) == 0)
    #define EAGAIN           WSAEWOULDBLOCK
    #define EWOULDBLOCK      WSAEWOULDBLOCK
    #define EISCONN          WSAEISCONN
    #define EINPROGRESS      WSAEINPROGRESS
    #define EINVAL           WSAEINVAL
    #define EALREADY         WSAEALREADY
    #else
    #define set_nonblock(fd) (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) != -1)
    #endif
    

不值一提

单独使用非阻塞套接字,如果您设法在各种系统上使用单个线程维护数千个连接,而无需进行少量调整,我一点也不感到惊讶。但是,这种模型并不理想,因为您需要一个繁忙的循环来循环遍历每个套接字,并在每个循环中即时测试它们的事件;例如,而不是在事件到达时触发您的代码被操作系统唤醒。

我们知道为了向应用程序发送事件,内核需要处理这些事件,所以我们可以给它一些时间,例如使用sleep(0);,作为快速修复。这肯定会看到 CPU 使用率从接近 100% 下降到 10% 以下。但是,另一种方法是使用非阻塞(或超时中断)函数多路复用多个(阻塞或非阻塞)套接字,这样函数在某些数据可用时立即返回,或者将等到时间到期接收数据。

select 有明显的优点,但也有缺点;也就是说,这些集合通常仅限于少量的套接字;要支持大量套接字,您需要在循环中设置一个循环,因为您会发现 64 个套接字限制(或其他任何限制)很快就会用完。此外,它不能解决 connect 阻塞问题(就像 O_NONBLOCK 和 ~FIONBIO` 方法所做的那样)。

因此,我不再谈论select;我将描述您可以使用的其他选项。另一个有类似限制的例子是poll;我也不会谈论这个。如果你想知道,互联网上有很多关于它的信息......


请注意,从现在开始的所有内容都非常不可移植(尽管您可能会找到将它们全部包装到一个通用接口中的方法,就像 curl multi 所做的那样)。


异步套接字调用将开始一个连接,然后像非阻塞套接字调用一样立即返回,除了它们还会在连接完成时引发信号或调用您指定的函数。这是让操作系统控制在事件到达时通知您的代码,而不是让操作系统等待您。应该清楚的是,就优化而言,异步套接字是理想的,但它们不可移植。每个操作系统都有不同的选项:

所有这些都有一个共同点,那就是它们在成功或失败时调用一个函数(或引发一个信号,您可以将其转换为对函数的调用)。但是,它们的界面并不是那么熟悉。

通常,我没有费心为它们编写任何类型的包装器,因为我发现我在这个答案开头提到的非阻塞套接字现在已经绰绰有余了。重要的是我不需要将它移植到每个系统,因为……我懒得做那个!只有当有人向我展示该系统运行缓慢时,我才会针对该系统进行优化。否则,我们最终会陷入海量的系统中,人们甚至可能永远不会在……上使用我们的软件。

【讨论】:

    猜你喜欢
    • 2015-04-21
    • 2022-09-25
    • 1970-01-01
    • 1970-01-01
    • 2019-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-23
    相关资源
    最近更新 更多