我有一个tcl脚本,可以串行运行多个shell命令。
这样的事情:
abc.tcl
command 1
command 2
command 3
...
command n
此脚本按以下格式将这些命令的输出打印到文本文件中:
### ### ### ### ### ###
Command name
### ### ### ### ### ###
Command Output
### ### ### ### ### ##
我试图让脚本运行得更快,但是让shell命令并行运行而不是串行运行。通过将它们推到后台(命令a&)。但我不知道如何保留输出文本文件的格式,就像以前一样。
当我将命令推送到后台时,我不得不将它们的输出附加到临时文件中,但这些文件只是将转储中的命令输出放在一起。很难区分不同的产出。
有没有我可以将在后台运行的每个命令的输出重定向到单个临时文件(也许临时文件的名称可以具有后台运行进程的进程ID)。一旦所有命令都运行,我可以将输出结合到正确的格式中吗?关于如何实现这一目标的任何想法/建议。
答案 0 :(得分:3)
如果命令没有相互依赖的状态,则可以将它们并行化。有很多方法可以做到这一点,但其中一个更容易的是使用线程包的thread pooling(这需要一个线程Tcl,现在许多平台上的规范):
package require Thread
set pool [tpool::create -maxworkers 4]
# The list of *scripts* to evaluate
set tasks {
{command 1}
{command 2}
...
{command n}
}
# Post the work items (scripts to run)
foreach task $tasks {
lappend jobs [tpool::post $pool $task]
}
# Wait for all the jobs to finish
for {set running $jobs} {[llength $running]} {} {
tpool::wait $pool $running running
}
# Get the results; you might want a different way to print the results...
foreach task $tasks job $jobs {
set jobResult [tpool::get $pool $job]
puts "TASK: $task"
puts "RESULT: $jobResult"
}
主要的tweakable是线程池的大小,默认为4的限制。(通过-maxworkers
选项将其设置为我在上面明确列出的tpool::create
。)最好的值可供选择取决于您获得的CPU核心数以及每个任务平均产生的CPU负载量;你需要测量和调整......
您还可以使用-initcmd
选项使用您选择的脚本预加载池中的每个工作线程。这是放置package require
电话的好地方。工人们完全完全彼此独立并且与主线程无关;他们不分享国家。如果你在一个单独的进程中运行每一段代码,你会得到相同的模型(但是你最终会编写更多代码来进行协调)。
[编辑]:这是一个适用于Tcl 8.4并使用子流程的版本。
namespace eval background {}
proc background::task {script callback} {
set f [open |[list [info nameofexecutable]] "r+"]
fconfigure $f -buffering line
puts $f [list set script $script]
puts $f {fconfigure stdout -buffering line}
puts $f {puts [list [catch $script msg] $msg]; exit}
fileevent $f readable [list background::handle $f $script $callback]
}
proc background::handle {f script callback} {
foreach {code msg} [read $f] break
catch {close $f}
uplevel "#0" $callback [list $script $code $msg]
}
proc accumulate {script code msg} {
puts "#### COMMANDS\n$script"
puts "#### CODE\n$code"
puts "#### RESULT\n$msg"
# Some simple code to collect the results
if {[llength [lappend ::accumulator $msg]] == 3} {
set ::done yes
}
}
foreach task {
{after 1000;subst hi1}
{after 2000;subst hi2}
{after 3000;subst hi3}
} {
background::task $task accumulate
}
puts "WAITING FOR TASKS..."
vwait done
注意:任务是生成结果的Tcl命令,但不能打印结果;结构代码(在background::task
中)处理。这些是子过程;它们彼此之间没有任何共享,因此您希望它们执行或配置的任何内容都必须作为任务的一部分发送。一个更复杂的版本可以保持一个热的子进程池,并且通常非常像一个线程池(受到由于在子进程而不是线程中的细微差别),但这比我想在这里编写的代码更多
结果代码(即异常代码)对于“ok”为0,对于“error”为1,在不常见的情况下为其他值。它们正是Tcl 8.6 catch
manual page中记录的值;由你来正确解释它们。 (我想我还应该添加代码,以便在出现错误的情况下报告::errorInfo
和::errorCode
变量内容,但这会使代码更加复杂......)