在 goroutine 中使用 exec.CommandContext 时如何调用 cancel()

2023-12-26

我想按需取消正在运行的命令,为此,我正在尝试,exec.CommandContext https://golang.org/pkg/os/exec/#CommandContext,目前正在尝试这个:

https://play.golang.org/p/0JTD9HKvyad https://play.golang.org/p/0JTD9HKvyad

package main

import (
    "context"
    "log"
    "os/exec"
    "time"
)

func Run(quit chan struct{}) {
    ctx, cancel := context.WithCancel(context.Background())
    cmd := exec.CommandContext(ctx, "sleep", "300")
    err := cmd.Start()
    if err != nil {
        log.Fatal(err)
    }

    go func() {
        log.Println("waiting cmd to exit")
        err := cmd.Wait()
        if err != nil {
            log.Println(err)
        }
    }()

    go func() {
        select {
        case <-quit:
            log.Println("calling ctx cancel")
            cancel()
        }
    }()
}

func main() {
    ch := make(chan struct{})
    Run(ch)
    select {
    case <-time.After(3 * time.Second):
        log.Println("closing via ctx")
        ch <- struct{}{}
    }
}

我面临的问题是cancel()被调用但进程没有被杀死,我的猜测是主线程先退出并且不等待cancel()正确终止命令,主要是因为如果我使用time.Sleep(time.Second)在结束时main它退出/终止正在运行的命令。

关于我如何能有任何想法wait确保该命令在退出之前已被终止,而不是使用sleep?可以吗cancel()成功杀死命令后在通道中使用?

在尝试使用单个 goroutine 时,我尝试了以下操作:https://play.golang.org/p/r7IuEtSM-gL https://play.golang.org/p/r7IuEtSM-gL但是cmd.Wait()似乎一直在阻塞select并且无法致电cancel()


在Go中,如果结束则程序将停止main方法(在main包)已到达。 Go 语言规范中描述了此行为程序执行部分 https://golang.org/ref/spec#Program_execution(强调我自己的):

程序执行开始于初始化main封装然后调用函数main。当该函数调用返回时,程序退出。它不会等待其他(非主)goroutines 完成。


Defects

我将考虑您的每个示例及其相关的控制流缺陷。您将在下面找到 Go Playground 的链接,但这些示例中的代码不会在限制性 Playground 沙箱中执行,因为sleep找不到可执行文件。复制并粘贴到您自己的环境中进行测试。

多个 Goroutine 示例

case <-time.After(3 * time.Second):
        log.Println("closing via ctx")
        ch <- struct{}{}

在计时器触发并且您向 goroutine 发出信号后,是时候杀死孩子并停止工作了,没有什么会导致main方法阻止并等待此完成,因此它返回。根据语言规范,程序退出。

调度程序可能会在通道传输后触发,因此之间可能存在竞争main退出并且其他 goroutine 醒来接收来自ch。然而,假设任何特定的行为交错是不安全的——而且,出于实际目的,之前不太可能发生任何有用的工作main退出。这sleep子进程将是orphaned https://en.wikipedia.org/wiki/Orphan_process;在 Unix 系统上,操作系统通常会将进程重新定位到init过程。

单个 goroutine 示例

在这里,你遇到了相反的问题:main不返回,因此子进程不会被终止。这种情况只有在子进程退出时(5分钟后)才能解决。出现这种情况是因为:

  • 致电给cmd.Wait in the Run方法是一个阻塞调用(docs https://golang.org/pkg/os/exec/#Cmd.Wait). The select语句被阻塞等待cmd.Wait返回错误值,因此无法从quit渠道。
  • The quit通道(声明为ch in main) is an 无缓冲通道。无缓冲通道上的发送操作将阻塞,直到接收器准备好接收数据。来自频道语言规范 https://golang.org/ref/spec#Channel_types(再次强调我自己的):

    容量(以元素数量表示)设置通道中缓冲区的大小。如果容量为零或不存在,则通道无缓冲并且只有当发送方和接收方都准备好时,通信才能成功.

    As Run被封锁在cmd.Wait,没有准备好的接收器来接收通道上传输的值ch <- struct{}{}中的声明main方法。main阻止等待传输此数据,从而阻止进程返回。

我们可以通过小的代码调整来演示这两个问题。

cmd.Wait正在阻塞

暴露阻塞本质cmd.Wait,声明以下函数并用它代替Wait称呼。该函数是一个包装器,其行为与cmd.Wait,但是打印 STDOUT 发生的情况会产生额外的副作用。 (游乐场链接 https://play.golang.org/p/BTN9i_9oW9d):

func waitOn(cmd *exec.Cmd) error {
    fmt.Printf("Waiting on command %p\n", cmd)
    err := cmd.Wait()
    fmt.Printf("Returning from waitOn %p\n", cmd)
    return err
}

// Change the select statement call to cmd.Wait to use the wrapper
case e <- waitOn(cmd):

运行这个修改后的程序后,您将观察到输出Waiting on command <pointer>到控制台。定时器触发后,您将观察输出calling ctx cancel,但没有对应的Returning from waitOn <pointer>文本。这种情况只会在子进程返回时发生,您可以通过将睡眠持续时间减少到更小的秒数(我选择 5 秒)来快速观察到这一点。

在退出频道上发送,ch, blocks

main无法返回,因为用于传播退出请求的信号通道未缓冲并且没有相应的侦听器。通过改变线路:

    ch := make(chan struct{})

to

    ch := make(chan struct{}, 1)

通道上的发送main将继续(到通道的缓冲区)并且main将退出——与多个 goroutine 示例的行为相同。然而,这个实现仍然有问题:在实际开始停止子进程之前,不会从通道的缓冲区读取该值main返回,因此子进程仍将是孤立进程。


固定版本

我已经为您制作了一个固定版本,代码如下。还有一些风格上的改进可以将您的示例转换为更惯用的 go:

  • 不需要通过通道间接发出何时停止的信号。相反,我们可以通过将上下文和取消函数的声明提升到main方法。可以在适当的时候直接取消上下文。

    我保留了单独的Run函数来演示以这种方式传递上下文,但在许多情况下,其逻辑可以嵌入到main方法,生成一个 goroutine 来执行cmd.Wait阻止呼叫。

  • The select中的声明main方法是不必要的,因为它只有一个case陈述。
  • sync.WaitGroup https://golang.org/pkg/sync/#WaitGroup引入是为了明确地解决以下问题main在子进程(在单独的 goroutine 中等待)被终止之前退出。等待组实现一个计数器;打电话给Wait阻塞直到所有 goroutine 完成工作并调用Done.
package main

import (
    "context"
    "log"
    "os/exec"
    "sync"
    "time"
)

func Run(ctx context.Context) {
    cmd := exec.CommandContext(ctx, "sleep", "300")
    err := cmd.Start()
    if err != nil {
        // Run could also return this error and push the program
        // termination decision to the `main` method.
        log.Fatal(err)
    }

    err = cmd.Wait()
    if err != nil {
        log.Println("waiting on cmd:", err)
    }
}

func main() {
    var wg sync.WaitGroup
    ctx, cancel := context.WithCancel(context.Background())

    // Increment the WaitGroup synchronously in the main method, to avoid
    // racing with the goroutine starting.
    wg.Add(1)
    go func() {
        Run(ctx)
        // Signal the goroutine has completed
        wg.Done()
    }()

    <-time.After(3 * time.Second)
    log.Println("closing via ctx")
    cancel()

    // Wait for the child goroutine to finish, which will only occur when
    // the child process has stopped and the call to cmd.Wait has returned.
    // This prevents main() exiting prematurely.
    wg.Wait()
}

(游乐场链接 https://play.golang.org/p/_i313PNpmKJ)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 goroutine 中使用 exec.CommandContext 时如何调用 cancel() 的相关文章

  • GAE Go — 如何对不存在的实体键使用 GetMulti?

    我发现自己需要做一个GetMulti使用键数组进行操作 其中某些实体存在 但有些实体不存在 我当前的代码 如下 返回错误 datastore no such entity err datastore GetMulti c keys info
  • GoQt 致命错误:QAbstractAnimation:没有这样的文件或目录

    我尝试编译 Qt 来开发桌面应用程序 我按照 Qt 网站上的官方 wiki 指南的说明进行操作 当我尝试go run示例文件夹中的示例 我收到错误 去运行 home pinkya rabbit workspace go1programs s
  • 鸭子在 Go 中打字

    我想写一个Join函数接受任意对象String 方法 package main import fmt strings type myint int func i myint String string return fmt Sprintf
  • 这两种方式哪一种是惯用的方式? time.Sleep() 还是自动收报机?

    我必须每分钟执行一些语句 我不确定我应该遵循以下哪一项 如果有人能解释内存和 CPU 方面的优缺点 那就太好了 时间 Sleep func main go func for time Sleep time Minute fmt Printl
  • 如何将 int[] 转换为 uint8[]

    所以 我需要你的帮助 我找不到关于该主题的任何内容 Golang 是一门刚刚诞生的语言 所以对于像我这样的新手来说很难快速找到答案 预先声明的 Goint类型大小是特定于实现的 32 位或 64 位 数字类型 http golang org
  • 如何在 Linux 中编写文本模式 GUI? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 当我编写脚本 程序时 我经常想弹出一个简单的文本 gui 来提示输入 我该怎么做 例如 来自 Shel
  • Golang中按长度分割字符串

    有谁知道如何在 Golang 中按长度分割字符串 例如 每 3 个字符分割 helloworld 那么理想情况下它应该返回一个 hel low orl d 数组 或者 一个可能的解决方案是在每 3 个字符后附加一个换行符 所有的想法都非常感
  • 如何读取大型平面文件

    我有一个平面文件 其中包含 339276 行文本 大小为 62 1 MB 我试图读入所有行 根据我所拥有的某些条件解析它们 然后将它们插入数据库 我最初尝试使用 bufio Scan 循环和 bufio Text 来获取该行 但缓冲区空间不
  • 与通道相比,sync.WaitGroup 的优势是什么?

    我正在开发一个并发 Go 库 我偶然发现了 goroutine 之间两种不同的同步模式 其结果相似 等待组 https play golang org p ZYPLlcp16TZ package main import fmt sync t
  • 解压文件的简单方法

    有没有一种简单的方法可以用 Go 解压文件 现在我的代码是 func Unzip src dest string error r err zip OpenReader src if err nil return err defer r Cl
  • 在 Golang 中运行外部 python,捕获连续的 exec.Command Stdout

    所以我的 go 脚本将像这样调用外部 python cmd exec Command python game py cmd Stdout os Stdout cmd Stderr os Stderr go func err cmd Run
  • Golang 中的确定性 RSA 加密 - 如何在多次加密下为给定消息获得相同的结果

    对于下面的RSA加密代码 每次对同一条消息进行加密时 结果都会不同 我发现这是由于rand Reader in the rsa EncryptOAEP功能使其更加安全doc https pkg go dev crypto rsa Encry
  • 实现具有更广泛方法签名的接口

    在Go中 是否有一种方法可以使用方法来实现接口 其中实现中相应方法的返回类型 比 预期返回类型 更宽 这很难解释 所以这里有一个例子 在 Go Playground 中运行以下示例代码时出现此错误 prog go 36 14 cannot
  • 在复杂的文件夹结构中进行测试

    我正在 golang 中构建一个设计模式存储库 为了运行所有测试 我使用这个 bash 脚本 有用 bin bash go test creational abstract factory go go test creational bui
  • 单值上下文中的多值错误

    我在编译 GO 代码时遇到此错误 multiple value fmt Println in single value context 我正在尝试创建一个函数 该函数接受可变数量的整数并将每个变量打印在一行上 GO package main
  • 将产生 goroutine 的 golang 方法

    据我所知 如果 goroutine 太忙 它们会阻止其他 goroutine 运行 对我来说 这意味着我的应用程序的性能和响应能力可能取决于我知道哪些库方法将控制其他 goroutine 例如通常是 Read 和 Write 有什么方法可以
  • 关闭长度未知的通道

    当不了解频道时我无法关闭频道 length package main import fmt time func gen ch chan int var i int for time Sleep time Millisecond 10 ch
  • pprof 和 ps 之间的内存使用差异

    我一直在尝试分析用 cobra 构建的 cli 工具的堆使用情况 这pprof工具显示如下 Flat Flat Sum Cum Cum Name Inlined 1 58GB 49 98 49 98 1 58GB 49 98 os Read
  • 指针上定义的方法仍然可以用值调用

    Effective Go 文档说明如下 关于接收者的指针与值的规则是 可以在指针和值上调用值方法 但只能在指针上调用指针方法 http tip golang org doc effective go html pointers vs val
  • 如何拥有在标准输出上更新的就地字符串

    我想输出到标准输出并让输出 覆盖 以前的输出 例如 如果我输出On 1 10 我想要下一个输出On 2 10覆盖On 1 10 我怎样才能做到这一点 stdout是一个流 io Writer 您无法修改已写入其中的内容 什么can更改的是该

随机推荐