我想跟踪一些长时间运行的进程的执行情况,并显示用户完成百分比和错误(如果有).如果是one个长时间运行的进程,那么就很容易了——您可以为进度(百分比)和错误创建通道.当我们有X个长时间运行的进程时,实现这种逻辑的正确方法是什么?

下面是一段有效的代码,但我真的不喜欢它的实现方式.

这是惯用的解决方案吗?将ProgressTracker作为通道传递给函数会更容易,但我不知道在这种情况下如何正确发送"进度"、"错误"和"完成"事件.

代码如下,围棋场也有相同的代码:https://go.dev/play/p/f3hXJsZR9WV

package main

import (
    "errors"
    "fmt"
    "strings"
    "sync"
    "time"
)

type ProgressTracker struct {
    Progress chan int
    Error    chan error
    Complete chan bool
    Url      string
}

/**
This method sleeps for 1 second and sends progress (in %) in each iteration to Progress channel
For .net sites on 3rd iteration fail with error
When everything is completed, send a message to Complete channel
*/
func work(url string, tracker *ProgressTracker) {
    tracker.Url = url
    fmt.Printf("processing url %s\n", url)
    for i := 1; i <= 5; i++ {
        time.Sleep(time.Second)
        if i == 3 && strings.HasSuffix(url, ".net") {
            tracker.Error <- errors.New("emulating error for .net sites")
            tracker.Complete <- true
        }
        progress := 20 * i
        tracker.Progress <- progress
    }
    tracker.Complete <- true
}

func main() {
    var trackers []*ProgressTracker
    var urls = []string{"google.com", "youtube.com", "someurl.net"}
    var wg sync.WaitGroup
    
    wg.Add(len(urls))
    for _, url := range urls {
        tracker := &ProgressTracker{
            Progress: make(chan int),
            Error:    make(chan error),
            Complete: make(chan bool),
        }
        trackers = append(trackers, tracker)
        go func(workUrl string, progressTracker *ProgressTracker) {
            work(workUrl, progressTracker)
        }(url, tracker)
    }

    go func() {
        wg.Wait()
    }()

    var processed = 0

    //iterate through all trackers and select each channel.
    //Exit from this loop when number of processed requests equals the number of trackers
    for {
        for _, t := range trackers {
            select {
            case pr := <-t.Progress:
                fmt.Printf("Url = %s, progress = %d\n", t.Url, pr)
            case err := <-t.Error:
                fmt.Printf("Url = %s, error = %s\n", t.Url, err.Error())
            case <-t.Complete:
                fmt.Printf("Url = %s is completed\n", t.Url)
                processed = processed + 1
                if processed == len(trackers) {
                    fmt.Printf("Everything is completed, exit")
                    return
                }
            }
        }
    }
}

UPD:

func work(url string, tracker *ProgressTracker) {
    tracker.Url = url
    fmt.Printf("processing url %s\n", url)
    for i := 1; i <= 5; i++ {
        if url == "google.com" {
            time.Sleep(time.Second * 3)
        }
        time.Sleep(time.Second)
        if i == 3 && strings.HasSuffix(url, ".net") {
            tracker.Error <- errors.New("emulating error for .net sites")
            tracker.Complete <- true
            return
        }
        progress := 20 * i
        tracker.Progress <- progress
    }
    tracker.Complete <- true
}

推荐答案

感谢您的回答!我提出了这种方法,它满足了我的需要:

package main

import (
    "errors"
    "fmt"
    "strings"
    "sync"
    "time"
)

type ProgressTracker struct {
    Progress  int
    Error     error
    Completed bool
    Url       string
}

/**
This method sleeps for 1 second and sends progress (in %) in each iteration to Progress channel
For .net sites on 3rd iteration fail with error
When everything is completed, send a message to Complete channel
*/
func work(url string, tracker chan ProgressTracker) {
    var internalTracker = ProgressTracker{
        Url: url,
    }
    tracker <- internalTracker
    fmt.Printf("processing url %s\n", url)
    for i := 1; i <= 5; i++ {
        if url == "google.com" {
            time.Sleep(time.Second * 3)
        }
        time.Sleep(time.Second)
        if i == 3 && strings.HasSuffix(url, ".net") {
            internalTracker.Error = errors.New("error for .net sites")
            internalTracker.Completed = true
            tracker <- internalTracker
            return
        }
        progress := 20 * i
        internalTracker.Progress = progress
        internalTracker.Completed = false
        tracker <- internalTracker
    }
    internalTracker.Completed = true
    tracker <- internalTracker
}

func main() {
    var urls = []string{"google.com", "youtube.com", "someurl.net"}
    var tracker = make(chan ProgressTracker, len(urls))
    var wg sync.WaitGroup
    wg.Add(len(urls))

    for _, url := range urls {
        go func(workUrl string) {
            defer wg.Done()
            work(workUrl, tracker)
        }(url)
    }

    go func() {
        wg.Wait()
        close(tracker)
        fmt.Printf("After wg wait")
    }()

    var completed = 0

    for completed < len(urls) {
        select {
        case t := <-tracker:
            if t.Completed {
                fmt.Printf("Processing for %s is completed!\n", t.Url)
                completed = completed + 1
            } else {
                fmt.Printf("Processing for %s is in progress: %d\n", t.Url, t.Progress)
            }
            if t.Error != nil {
                fmt.Printf("Url %s has errors %s\n", t.Url, t.Error)
            }
        }

    }
}

在这里,我将ProgressTracker作为通道传递(ProgressTracker中的字段声明为简单字段,而不是通道),在每个事件上,从work函数返回正在发生的事情的完整状态(如果进度增加-设置新值并返回a struct

Go相关问答推荐

Go -SDP服务器读缓冲区不会更改任何内容

错误.如果它包含切片,则返回FALSE

如何在S汇编器中更高效地将全局数据加载到霓虹灯寄存器?

Golang在不写入磁盘的情况下为jpeg图像生成一致的哈希

当我有外键时,如何使用 GORM 创建数据库的新条目

Golang Gorm Fiber - 如何将定义为别名的名称发送到索引模板?

golang yaml 马歇尔网址

将值发送到 Channel 并在就绪时读取输出

Get 请求在 Thunder 客户端/Postman 中返回数据,但在 Golang 代码中给出空白数据

拆分文本并按空格获取字符串数组,如果文本长度超过 500,则获取字符串数组

致命错误:找不到由 zergon321/reisen 引起的libavcodec/avcodec.h文件

使用 go.work 文件在多个测试文件上运行 go test 命令

如何将文件上传到 Google Drive,并与使用服务帐户和 Golang 的任何人共享

Golang - 将 [8] 布尔转换为字节

Golang grpc go.mod 问题

如何从 Go 1.18 中的单个方法返回两种不同的具体类型?

实现接口的指针的泛型类型是什么?

Golang 有类似 C++ 的 decltype 的东西吗?

Beego - 我需要context.Context而不是 Beego 上下文

将函数的值作为输入参数返回给另一个