并行执行嵌套任务,不保证线程安全。每个任务将在其自己的线程中运行,并发问题的可能性随着主机系统上 CPU 数量的增加而增加。
警告:虽然 Apache Ant 核心被认为是线程安全的,但对于任务没有这样的保证,这些任务在 Ant 的测试过程中没有经过线程安全测试。第三方任务可能是也可能不是线程安全的,Ant 的一些核心任务(例如 <javac>
)肯定不是可重入的。这是因为它们使用从未设计为在多线程环境中使用的库。
<parallel>
的主要用例是同时运行外部程序,例如应用程序服务器以及 JUnit 或 TestNG 测试套件。任何尝试并行运行大型 Ant 任务序列的人(例如,同时运行 javadoc
和 javac
),都隐式地承担了识别和修复他们运行的任务中的所有并发错误的任务。
因此,虽然此任务有其用途,但应将其视为高级任务,应在某些批处理或测试情况下使用,而不是在多核 CPU 上加速构建时间的简单技巧。
属性 | 描述 | 必需 |
---|---|---|
threadCount | 要使用的最大线程数。 | 否 |
threadsPerProcessor | 每个可用处理器要使用的最大线程数(Java 1.4+) | 否;默认使用 threadCount |
timeout | 执行终止前的毫秒数 | 否 |
failonany | 如果任何嵌套任务失败,任务执行将在此时完成,而不会等待任何其他任务完成。 | 否;默认值为 false。 |
pollInterval | 目前没有效果 | 否;默认值为 1000 |
并行任务在 Ant 构建文件中有多种用途,包括
任何有效的 Ant 任务都可以嵌入到 parallel
任务中,包括其他 parallel
任务,尽管不能保证这些任务在这样的环境中是线程安全的。
在 parallel
任务中的任务运行时,主线程将被阻塞,等待所有子线程完成。如果执行因超时或嵌套任务失败而终止(当 failonany 标志设置时),parallel
任务将完成,而不会等待其他线程中的其他嵌套任务完成。
如果 <parallel>
任务中的任何任务失败,并且未设置 failonany,则其他线程中的剩余任务将继续运行,直到所有线程都完成。在这种情况下,parallel
任务也将失败。
parallel
任务可以与 sequential 任务结合使用,以定义要在并行块中每个线程上执行的任务序列。
可以使用 threadCount 属性来设置执行的可用线程的最大数量。如果不存在,所有子任务将立即执行。如果存在,则并发执行的任务的最大数量将不会超过指定的线程数。此外,每个任务将按给定的顺序启动。但是,对于任务的执行速度或完成顺序没有保证,只是保证每个任务将在下一个任务启动之前启动。
如果您使用的是 Java 1.4 或更高版本,您还可以使用 threadsPerProcessor,并且可用线程数将是处理器数量的启动倍数(但是,没有与特定处理器的亲和性)。这将覆盖 threadCount 中的值。如果在任何旧的 JVM 上指定了 threadsPerProcessor,则将按原样使用 threadCount 中的值。
在使用 threadCount 和 threadsPerProcessor 时,应注意确保构建不会死锁。这可能是由于诸如 waitfor
之类的任务在会解锁 waitfor
的任务发生之前占用所有可用线程造成的。这不是 Java 语言级线程语义的替代方案,最适合用于“令人尴尬的并行”任务。
parallel
任务支持 <daemons>
嵌套元素。这是一个要在并行守护线程中运行的任务列表。parallel
任务不会等待这些任务完成。但是,作为守护线程,它们不会阻止 Ant 完成,此时线程将被终止。在 parallel
任务本身完成之前发生的守护线程中的故障将被报告,并且可能导致 parallel
抛出异常。在 parallel
完成后发生的故障不会被报告。
例如,可以使用守护线程来启动可能无法从 Ant 轻松终止的测试服务器。通过使用 <daemons>
,此类服务器不会阻止构建。
这是测试服务器应用程序的典型模式。在一个线程中,服务器启动(<wlrun>
任务)。另一个线程包含三个按顺序执行的任务。<sleep>
任务用于为服务器留出启动时间。另一个能够验证服务器是否可用的任务可以用来代替 <sleep>
任务。然后,<junit>
测试工具运行,同样在其自己的 JVM 中。测试完成后,服务器停止(在本例中使用 <wlstop>
),允许两个线程都完成。<parallel>
任务也将在此时间完成,然后构建将继续。
<parallel> <wlrun ... > <sequential> <sleep seconds="30"/> <junit fork="true" forkmode="once" ... > <wlstop/> </sequential> </parallel>
在这里,两个独立的任务运行以在构建期间实现更好的资源利用率。在本例中,一些 servlet 在一个线程中编译,而一组 JSP 在另一个线程中预编译。开发人员需要注意,这两个任务是独立的,无论是它们的依赖关系还是它们在 Ant 的外部环境中的潜在交互。在这里,我们为 <javac>
任务设置了 fork=true
,以便它在新的进程中运行;如果 <wljspc>
任务在 VM 中使用 javac 编译器(它可能),则可能会出现并发问题。
<parallel> <javac fork="true"...> <!-- compiler servlet code --> <wljspc ...> <!-- precompile JSPs --> </parallel>
此示例代表了使用 threadCount 和 threadsPerProcessor 属性的典型需求。启动所有 40 个任务可能会使系统因内存和 CPU 时间而瘫痪。通过限制并发执行的数量,您可以减少对 CPU、内存和磁盘 IO 的争用,从而实际上更快地完成。这也是使用 threadCount(以及可能使用 threadsPerProcessor)的良好候选,因为每个任务都是独立的(每个新的 JVM 都被派生)并且没有依赖于其他任务。
<macrodef name="dbpurge"> <attribute file="file"/> <sequential> <java jar="utils/dbpurge.jar" fork="true" > <arg file="@{file}/> </java> </sequential> </macrodef> <parallel threadCount="4"> <dbpurge file="db/one"/> <dbpurge file="db/two"/> <dbpurge file="db/three"/> <dbpurge file="db/four"/> <dbpurge file="db/five"/> <dbpurge file="db/six"/> <dbpurge file="db/seven"/> <dbpurge file="db/eight"/> <!-- repeated about 40 times --> </parallel>