multithreading - 使用多线程从URL下载文件

标签 multithreading curl coldfusion

我需要从网址下载excel文件列表,并将其保存在文件夹中。 (最多可以包含200个文件。)我从以下代码开始循环并下载每个文件:

<cfloop query="idsToDownload">
    <cfset fileURL = "https://myLink/#downloadID#" /><!--- link to an xlsx file --->
    <cfexecute name="curl" arguments = "#fileURL# -k" timeout="10" outputFile="#downloadID#.xlsx" />
</cfloop>

这将下载每个文件,并将它们保存在coldfusion temp目录中。 (目前,仅用于测试-最终,我们将确定它们的存储位置,并使用路径更新outputFile。)这很好,除了最终达到cfloop时间限制(下载大约30个文件之后)。但是无论如何,我们确实希望为每次下载启动一个线程以最大化效率。因此,我在循环内添加了一个cfthread标记(免责声明:我是cfthreading的新手):
<cfloop query="idsToDownload">
    <cfthread name="download_#downloadID#" action="run">
        <cfset fileURL = "https://myLink/#downloadID#" />
        <cfexecute name="curl" arguments = "#fileURL# -k" timeout="10" outputFile="#downloadID#.xlsx" />
    </cfthread>
</cfloop>

我将假定它的执行方式与以前相同,只是每次下载都将在异步线程中运行。但是,当我运行此命令时,什么也没有发生。我在页面上没有任何错误,但是ColdFusion临时文件中没有文件显示(就像使用简单的无线程cfloop一样)。此代码有什么问题?

编辑:

我还尝试了一次下载一次的单线程,它可以正常工作:
<cfthread name="downloadFile" action="run">
    <cfset fileURL = "https://myLink/123" />
    <cfexecute name="curl" arguments = "#fileURL# -k" timeout="10" outputFile="123.xlsx" />
</cfthread>

所以cfloop/cfthread组合似乎出了点问题...

最佳答案

我认为您可能需要一种不同的方法来下载数百个文件。每个文件线程只会在目标服务器可能停止响应或阻止您(如果不受您控制)之前扩展得如此之高。另外,如果您使用的是cURL,那么您将为每个线程生成一个子进程,因此它占用大量资源。

相反,我会创建一个线程池并在它们之间分配工作。创建N个线程,并为每个线程提供要下载的文件列表。每个线程都将遍历列表,您可以轻松调整N以获得最佳性能/资源使用权衡。

上面方法的潜在潜在缺点是,如果一个文件列表的下载速度比其他文件列表的下载速度快得多,那么它将尽早结束,其余工作将由更少的线程执行。您可以实现一个工作跟踪器,每个线程都调用该工作跟踪器来拾取下一个要下载的文件。只要它的getNextFile()方法被适本地同步,它将使所有N个线程保持工作状态,直到有更多的工作要做。

另外,如果下载像示例中所示的那样简单,请考虑不使用cURL。考虑使用CFHTTP或Java HTTP Client库之一,因为您不必每次下载都产生一个进程。

编辑
关于运行现有代码,我能够构建一个相应的示例,该示例似乎执行正常(CF10/OSX):

Thread test...<br/>
<cfloop from="1" to="3" index="i">  
    <cfoutput>Starting #i# <br/></cfoutput><cfflush>
    <cfthread action="run" name="dl-thread-#i#" urlNumber="#i#">
        <cflog log="Application" text="#urlNumber#">
    <cfexecute name="/opt/local/bin/curl" arguments="https://www.google.co.uk/?q=#urlNumber#" outputfile="#GetTemplatePath()##urlNumber#.html" errorFile="#GetTemplatePath()##urlNumber#.html.err">
    <!--- alternatively....
    <cfhttp url="https://www.google.co.uk/?q=#urlNumber#" file="#urlNumber#.html" path="#GetDirectoryFromPath(GetTemplatePath())#" method="get" />
    --->
    </cfexecute>
</cfthread> 
</cfloop>
Done....

我可以看到的唯一真正的区别是,我正在将参数显式传递给线程,并允许线程代码使用这些参数来组装URL(请参阅urlNumber属性)。在我这样做之前,我看到了非常奇怪的结果:我为文件写入的值是2-4,而不是1-3。

我将确保线程需要的任何数据都被显式传递。此外,cfexecute上的文档指出name属性必须是绝对路径(包括扩展名),但是如果没有,您的代码似乎可以正常工作吗?

我提供了一个使用<cfhttp>来实现与curl相同的功能的注释示例。几乎可以肯定,启动任何外部过程数百次都不会扩展。修改上面的示例以拆分一个列表,该列表上的每个工作都应该简单明了。

编辑2
以下代码段实现了在可配置数量的线程之间分配工作负载:
Thread test...<br/>
<cfscript>
    urlCount=100;
    threads=5;
    urls=[];

    //utility function to split an array into a set of equal arrays
    function ArrayDivide(arr,divisor){
        divided=[];
        for(i=1;i<=divisor;i++){
            divided[i]=[];
        }
        for(i=1;i<=ArrayLen(arr);i++){
            ArrayAppend(divided[(i%divisor)+1],arr[i]);
            WriteOutput((i%divisor)+1 & "<br>");
        }
        return divided;
    }

    //Create a set of dummy URLs to test against
    //sleep.cfm waits for as long as it's asked to in order to simulate downloads taking a bit of time
    for(i=1;i<=urlCount;i++){
        urls[i]="http://localhost:8500/sleep.cfm?duration="&(i*50);
    }

    urlLists=ArrayDivide(urls,threads);

</cfscript>

<cfloop from="1" to="#threads#" index="i">  
    <cfoutput>Starting #i# <br/></cfoutput><cfflush>
    <cflog log="Application" text="#i# spawn">
    <cfthread action="run" name="dl-thread-#i#" urlList="#urlLists[i]#" threadNumber="#i#">
        <cflog log="Application" text="#threadNumber# start">
        <cfloop from="1" to="#ArrayLen(urlList)#" index="j">
            <cfhttp url="#urlList[j]#" file="#threadNumber#_#j#.html" path="#GetDirectoryFromPath(GetTemplatePath())#" method="get" />
        </cfloop>
        <cflog log="Application" text="#urlNumber# end">
</cfthread> 

</cfloop>
Done....

关于multithreading - 使用多线程从URL下载文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25886338/

相关文章:

c++ - 在 curl 和 c++ 中测量下载速度。我做得对吗?

coldfusion - 我应该在 CFC 中使用 CFINCLUDE 还是 CFSAVECONTENT

javascript - 如何根据用户在 HTML 选择中的选择更改字段?

java - 在 Windows 上线程直到 Thread.join() 才停止

bash - 将 curl 响应的特定部分保存到变量

java - 添加更多线程会提高程序的性能吗

BASH shell 脚本回显以在同一行输出

java - PHP 是否用于快速简便的网站,而其他语言用于更复杂的网站?

java - 同步 ArrayList

c - 当多线程程序创建新进程时会发生什么?