【问题标题】:Synchronization for several goroutines using channels使用通道同步多个 goroutine
【发布时间】:2018-10-23 02:43:45
【问题描述】:

我需要启动多个具有单个任务队列和单个结果队列的工作人员。每个工人应该在不同的 goroutine 中启动。而且我需要等到所有工作人员都完成并且任务队列为空才能退出程序。 我准备了 goroutine 同步的小例子。 主要思想是我们计算队列中的任务并等待所有工作人员完成工作。但是当前的实现有时会丢失值。 为什么会发生这种情况以及如何解决问题? 示例代码:

import (
    "fmt"
    "os"
    "os/signal"
    "strconv"
)

const num_workers = 5

type workerChannel chan uint64

// Make channel for tasks
var workCh workerChannel
// Make channel for task counter
var cntChannel chan int

// Task counter
var tskCnt int64

// Worker function
func InitWorker(input workerChannel, result chan string, num int) {
    for {
        select {
        case inp := <-input:
            getTask()
            result <- ("Worker " + strconv.Itoa(num) + ":" + strconv.FormatUint(inp, 10))
        }
    }
}

// Function to manage task counter
// should be in uniq goroutine
func taskCounter(inp chan int) {
    for {
        val := <-inp
        tskCnt += int64(val)
    }
}

// Put pask to the queue
func putTask(val uint64) {
    func() {
        fmt.Println("Put ", val)
        cntChannel <- int(1)
        workCh <- val
    }()
}

// Get task from queue
func getTask() {
    func() {
        cntChannel <- int(-1)
    }()
}

func main() {
// Init service channels
    abort := make(chan os.Signal)
    done := make(chan bool)

// init queue for results
    result := make(chan string)

// init task queue
    workCh = make(workerChannel)

// start some workers
    for i := uint(0); i < num_workers; i++ {
        go InitWorker(workCh, result, int(i))
    }

// init counter for synchro
    cntChannel = make(chan int)
    go taskCounter(cntChannel)

// goroutine that put some tasks into queue
    go func() {
        for i := uint(0); i < 21; i++ {
            putTask(uint64(i))
        }

        // wait for processing all tasks and close application
        for len(cntChannel) != 0 {}
        for tskCnt != 0 {}
        for len(workCh) != 0 {}
        for len(result) != 0 {}

        // send signal for close
        done <- true
    }()

    signal.Notify(abort, os.Interrupt)
    for {
        select {
        case <-abort:
            fmt.Println("Aborted.")
            os.Exit(0)

        // print results
        case res := <-result:
            fmt.Println(res)

        case <-done:
            fmt.Println("Done")
            os.Exit(0)
        }
    }
}

【问题讨论】:

    标签: go concurrency synchronization channel goroutine


    【解决方案1】:

    使用 sync.WaitGroup 等待 goroutine 完成。关闭通道以导致退出通道上的循环读取。

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type workerChannel chan uint64
    
    const num_workers = 5
    
    func main() {
    
        results := make(chan string)
        workCh := make(workerChannel)
    
        // Start workers
        var wg sync.WaitGroup
        wg.Add(num_workers)
        for i := 0; i < num_workers; i++ {
            go func(num int) {
                defer wg.Done()
                // Loop processing work until workCh is closed
                for w := range workCh {
                    results <- fmt.Sprintf("worker %d, task %d", num, w)
                }
    
            }(i)
        }
    
        // Close result channel when workers are done
        go func() {
            wg.Wait()
            close(results)
        }()
    
        // Send work to be done
        go func() {
            for i := 0; i < 21; i++ {
                workCh <- uint64(i)
            }
            // Closing the channel causes workers to break out of loop
            close(workCh)
        }()
    
        // Process results. Loop exits when result channel is closed.
        for r := range results {
            fmt.Println(r)
        }
    }
    

    https://play.golang.org/p/ZifpzsP6fNv

    【讨论】:

    • 有效!但是对于这种情况,任务将等到其中一名工人被释放。我想实现一个案例,当用于生成任务的 goroutine 能够将尽可能多的项目放入队列时,因为我们有可用的内存。所有这些都将与工人一一进行。可能吗?我的上一篇文章中描述了一个用例。评论
    • 增加workCh频道的大小是否足够?如果没有,则使用sync.Cond 实现一个没有大小限制的队列。
    • @CeriseLimón,如果您使用缓冲通道会有所不同吗?例如results := make(chan string, num_workers)我这里修改了你的例子:play.golang.org/p/6Jpjs887Wbc,好像结果是有序的,但是不明白为什么。
    • @StevenFerrer 由于实施的细节,结果是有序的。您的程序在其他订单中打印结果有效。
    • @CeriseLimón,感谢您的快速回复。你是对的,我用我的代码试过了,结果没有排序。我希望有一种方法可以真正使结果井然有序。类似于队列的东西,但仍保持作业的异步处理。
    【解决方案2】:

    我建议对这类任务使用 close(chan)。

    WaitGroup 版本。

    package main
    
    import (
        "log"
        "sync"
    )
    
    func worker(in chan int, wg *sync.WaitGroup) {
        defer wg.Done()
        for i := range in {
            log.Println(i)
        }
    
    }
    
    func main() {
        in := make(chan int)
        lc := 25
        maxValue := 30
        wg := sync.WaitGroup{}
        wg.Add(lc)
        for i := 0; i < lc; i++ {
            go worker(in, &wg)
        }
    
        for c := 0; c <= maxValue; c++ {
            in <- c
        }
        close(in)
        wg.Wait()
    }
    

    频道版本

    package main
    
    import (
        "log"
        "os"
    )
    
    func worker(in chan int, end chan struct{}) {
        defer func() { end <- struct{}{} }()
        for i := range in {
            log.Println(i)
        }
    
    }
    
    func main() {
        in := make(chan int)
        lc := 25
        maxValue := 30
        end := make(chan struct{})
        var fin int
        go func() {
            for {
                <-end
                fin++
                log.Println(`fin`, fin)
                if fin == lc {
                    break
                }
            }
            close(end)
            os.Exit(0)
        }()
        for i := 0; i < lc; i++ {
            go worker(in, end)
        }
    
        for c := 0; c <= maxValue; c++ {
            in <- c
        }
        close(in)
        <-make(chan struct{})
    }
    

    【讨论】:

    • 在这两个版本中,您都可以从工作人员内部关闭通道。这不是我的情况。在我的情况下,队列将在运行时更新,我不知道数据的结尾在哪里。假设远程主机枚举文件并向队列提供文件名。工作人员正在根据文件名计算哈希(该过程最多需要 30 秒)。我想启动一些将接收文件名并将它们放入队列的工作人员和 goroutine。每个工人都比收集器慢得多。在文件结束时,将收到特殊命令,我们必须等待队列完成。
    • 1.我从主通道关闭通道,而不是从工人关闭通道。在创建它的同一函数/对象中关闭资源是一个很好的拇指角色。 2. 关闭通道只为写入而关闭,所有缓存的消息都会继续处理。 3. 我没看懂这个案子。它是一个 HTTP 服务器?从发布/订阅队列中读取的守护进程?还是工作?
    猜你喜欢
    • 2013-05-21
    • 2015-06-03
    • 2020-08-02
    • 1970-01-01
    • 2019-10-08
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    相关资源
    最近更新 更多