在 tcl 中使用线程,需要执行脚本并查看脚本挂起的日志文件

Using Threads in tcl, need to execute the script and watch log file for script hung

我的场景是:使用tcl,我正在写一个文件。然后我正在获取该文件并希望监视该文件在执行期间将生成的日志文件。如果日志文件的大小在 2 小时后没有改变,那么我需要停止文件的执行并重新运行 tcl 脚本,它将重新生成文件然后获取它(生成源循环继续直到文件执行完成)

这是我的场景的伪代码:

set root /home/nikhil/
set group {all}
set TopScript [open $root/TopScript.tcl w] 
    puts $TopScript "[exec perl $root/extract_excel.pl $group] \n}"
    Puts $TopScript "set logfilename $root/logfile"
    puts $TopScript "source $root/main_1.tcl"
    puts $TopScript "source $root/main_2.tcl"
    close $TopScript

#Pseudo code for scenario what I want is:

thread::create {
   exec tclsh /home/nikhil/TopScript.tcl
   thread::wait
}

thread::create {
 set flag_for_interupt 0

  while{!flag_for_interupt} {
       set old_log_size [file size $root/logfile]
       after [expr {int(1000* 60* 60* 2)}]
       set new_log_size [file size $root/logfile]

       if{$old_log_size == $new_log_size} {
           puts "I suspect Test is in hung state.... checking again after 2 hours.....\n"
           after [expr {int(1000* 60* 60* 2)}]
           set $new_log_size [file size $root/logfile]
           if{$old_log_size == $new_log_size} {
               puts "\n\n Test is in hung state.... log has not updated since last 4 hours........\n\n"
           }
           ##########  enter code to interupt main thread and execute again
           set flag_for_inturept 1
        }

    } 
 }

Tcl 不在线程之间共享(正常)变量。相反,您需要通过在线程之间发送消息来工作。消息只是一个(通常很短的)脚本,您要求另一个线程 运行(脚本的结果可以通过几种方式处理,包括同步等待或通过 运行ning 脚本断开)。大多数时候,您在接收线程中设置一个过程来实际完成工作。

让我们重构您的等待线程,使其以这种方式运行:

set waiter [thread::create {
    proc do {filename targetThread returnMessage} {
        set delay [expr {int(1000* 60* 60* 2)}]
        while true {
            # This would be really a do-while loop, but we don't have those
            while true {
                set old_log_size [file size $filename]
                after $delay
                set new_log_size [file size $filename]
                if {$old_log_size == $new_log_size} break
            }

            puts "I suspect Test is in hung state... checking again after 2 hours...\n"

            after $delay
            set new_log_size [file size $filename]
            if {$old_log_size == $new_log_size} break
        }

        puts "\n\n Test is in hung state... log has not updated since last 4 hours...\n\n"

        # Send message to main thread to do something about the hung test
        thread::send -async $targetThread $returnMessage
    }
    thread::wait
}]

我们将该线程设置为实际工作方式如下:

thread::send -async $waiter [list do $root/logfile [thread::current] {set test_hung 1}]

然而,其中唯一的长操作是对 after 的调用。 (好吧,除非你非常不幸地调用 OS 来获取日志文件大小。)这意味着我们可以转换为在线程中使用 异步 形式,留下线程在工作时打开以供访问。

set waiter [thread::create {
    proc do {filename targetThread returnMessage} {
        set delay [expr {int(1000* 60* 60* 2)}]
        set old_log_size [file size $filename]
        # Schedule the run of do2 in two hours
        after $delay [list do2 $filename $targetThread $returnMessage $delay $filename $old_log_size]
    }

    proc do2 {filename targetThread returnMessage delay filename old_log_size} {
        set new_log_size [file size $filename]
        if {$old_log_size == $new_log_size} {
            puts "I suspect Test is in hung state... checking again after 2 hours...\n"

            # Schedule the run of do3 in another two hours
            after $delay [list do3 $filename $targetThread $returnMessage $delay $filename $old_log_size]
        } else {
            # An update did happen; run ourselves again in two hours to compare to the new size
            after $delay [list do2 $filename $targetThread $returnMessage $delay $filename $new_log_size]
        }
    }

    proc do3 {filename targetThread returnMessage delay filename old_log_size} {
        set new_log_size [file size $filename]
        if {$old_log_size == $new_log_size} {
            puts "\n\n Test is in hung state... log has not updated since last 4 hours...\n\n"

            # Send message to main thread to do something about the hung test
            thread::send -async $targetThread $returnMessage
        } else {
            # An update did happen; run ourselves again in two hours to compare to the new size
            after $delay [list do2 $filename $targetThread $returnMessage $delay $filename $new_log_size]
        }
    }
    thread::wait
}]

所以……我们有可管理性但失去了可读性(使用的 API 是相同的)。不错但不是很好! (这种重组被称为转换为连续传递形式,它往往会破坏代码的可读性。)在 8.6 中我们可以做得更好,因为我们有可以屈服于线程事件循环的协程。

set waiter [thread::create {
    proc do {filename targetThread returnMessage} {
        coroutine Coro[incr ::Coro] doBody $filename $targetThread $returnMessage
    }
    proc delayForTwoHours {} {
        set delay [expr {int(1000* 60* 60* 2)}]
        after $delay [info coroutine]
        yield
    }

    proc doBody {filename targetThread returnMessage} {
        while true {
            while true {
                set old_log_size [file size $filename]
                delayForTwoHours
                set new_log_size [file size $filename]
                if {$old_log_size == $new_log_size} break
            }

            puts "I suspect Test is in hung state... checking again after 2 hours...\n"

            delayForTwoHours
            set new_log_size [file size $filename]
            if {$old_log_size == $new_log_size} break
        }

        puts "\n\n Test is in hung state... log has not updated since last 4 hours...\n\n"

        # Send message to main thread to do something about the hung test
        thread::send -async $targetThread $returnMessage
    }
    thread::wait
}]

That(仍然具有相同的 API 调用约定)提供了可管理性,但几乎所有代码(特别是它们自己的过程中的短位除外)看起来与我编写的第一个版本相同。在幕后,协程将重写为连续传递形式,但现在由 Tcl 运行time 处理,而不需要在您的代码中显式完成。 (此外,Tcl 使用显式协程启动,但这反过来意味着它可以跨多个堆栈级别进行 yield,而无需某些其他语言的复杂 yield 链。)


我将使用第二个或第三个版本作为根本不需要额外线程的代码版本的基础作为练习。 运行 后台进程也不需要线程;整个管理过程可以只使用一个(用户可见的)线程。