【问题标题】:Running commands in background from TCL script and formatting output从 TCL 脚本在后台运行命令并格式化输出
【发布时间】:2012-10-26 05:43:07
【问题描述】:

我有一个连续运行多个 shell 命令的 tcl 脚本。

类似这样的:

abc.tcl

command 1
command 2 
command 3
...
command n

此脚本将这些命令的输出打印到以下格式的文本文件中:

### ### ### ### ### ###
Command name
### ### ### ### ### ###

Command Output

### ### ### ### ### ##

我试图让脚本运行得更快,但让 shell 命令并行而不是串行运行。通过将它们推到后台(命令 a &)。但是我不知道如何像以前一样保留输出文本文件的格式。

当我将命令推送到后台时,我不得不将它们的输出附加到一个临时文件中,但这些文件只是将命令的输出放在一起转储中。很难区分不同的输出。

有没有办法我可以将在后台运行的每个命令的输出重定向到一个单独的临时文件(也许临时文件的名称可以有后台运行进程的进程 ID)。一旦所有命令都运行了,我可以将输出集中到正确的格式吗?关于如何完成此任务的任何想法/建议。

【问题讨论】:

  • 作为替代方案,也许我可以在后台运行 shell 命令并将它们推送到各个临时文件,并在它们完成执行后得到通知,然后将它们放入主文件中。我担心在这里同时进入多个进程写入同一个文件,不希望输出文件损坏。而且真的不知道如何从 tcl 脚本中发出后台进程已完成运行的信号。
  • 你可以用fileevent来做;当阅读器管道不再有远端时,它们变得可读。 (作家阻止......)

标签: shell redirect background tcl


【解决方案1】:

如果命令没有相互依赖的状态,您可以将它们并行化。有很多方法可以做到这一点,但其中一种更简单的方法是使用线程包的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"
}

主要可调整的是线程池的大小,默认限制为 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)处理这个问题。这些是子流程;它们彼此不共享任何内容,因此您希望它们执行或配置的任何内容都必须作为任务的一部分发送。更复杂的版本可以保留子进程的热池,并且在一般情况下非常像线程池(由于处于子进程而不是线程中而存在细微差异),但这比我想在这里写的代码更多.

结果代码(即异常代码)为 0 表示“ok”,1 表示“error”,在不太常见的情况下为其他值。它们正是Tcl 8.6 catch manual page 上记录的值;由您来正确解释它们。 (我想我还应该添加代码以使::errorInfo::errorCode 变量内容在出现错误的情况下被报告回来,但这会使代码变得更加复杂......)

【讨论】:

  • FWIW,线程包应该随 Tcl 8.6 安装一起提供(至少从 8.6b2 开始),但是文档构建中有一个错误,这意味着线程文档没有继续运行www.tcl.tk 网站。
  • 非常感谢您提供的信息。线程池似乎是这样做的优雅方式。不幸的是,我现在正在运行 Tcl 8.4,并且没有线程包。还有其他方法可以完成类似的事情吗?
  • @egorulz 当然,您可以使用子流程来做到这一点。我需要花点时间研究一下细节……
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-04
  • 2015-07-26
相关资源
最近更新 更多