在tcl中使用Threads,需要执行脚本并观察脚本挂起的日志文件

时间:2017-11-16 06:57:53

标签: multithreading tcl build-automation

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

以下是我的方案的psedo代码:

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
        }

    } 
 }

1 个答案:

答案 0 :(得分:2)

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

让我们重新构建你的等待线程以这种方式运行:

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的调用。 (好吧,除非你非常不满意操作系统调用以获取日志文件大小。)这意味着我们可以转换为在线程中使用异步表单,让线程保持打开状态它正在发挥作用。

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是相同的)。不错,但不是很好! (这种重组被称为转换为Continuation-Passing Form,它往往会破坏代码的可读性。)在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
}]

那个(它仍然具有相同的API调用约定)提供了可管理性,但几乎保留了所有代码(特别是除了他们自己的过程中的短位),看起来与我写的第一个版本相同。在封面下,协程会重写为继续传递形式,但现在由Tcl运行时处理,而不是需要在代码中显式完成。 (此外,Tcl使用显式协程启动,但这反过来意味着它可以产生多个堆栈级别而没有其他语言的复杂产量链。)

我把它留作练习,使用第二个或第三个版本作为代码版本的基础,完全不需要额外的线程。在后台运行进程也不需要线程;整个管理流程只能使用一个(用户可见的)线程。