【问题标题】:Passing a WaitGroup to a function changes behavior, why?将 WaitGroup 传递给函数会改变行为,为什么?
【发布时间】:2021-09-26 03:35:33
【问题描述】:

我有 3 个合并排序实现:

MergeSort:没有并发的简单;

MergeSortSmart:并发受缓冲通道大小限制。如果缓冲区已满,则调用简单实现;

MergeSortSmartBug:与上一个策略相同,但有一个小的“重构”,将 wg 指针传递给函数以减少代码重复。

前两个按预期工作,但第三个返回一个空切片而不是排序的输入。我不明白发生了什么,也没有找到答案。

这里是代码的游乐场链接:https://play.golang.org/p/DU1ypbanpVi

package main

import (
    "fmt"
    "math/rand"
    "runtime"
    "sync"
)

type pass struct{}

var semaphore = make(chan pass, runtime.NumCPU())

func main() {
    rand.Seed(10)
    s := make([]int, 16)
    for i := 0; i < 16; i++ {
        s[i] = int(rand.Int31n(1000))
    }

    fmt.Println(s)
    fmt.Println(MergeSort(s))
    fmt.Println(MergeSortSmart(s))
    fmt.Println(MergeSortSmartBug(s))
}

func merge(l, r []int) []int {
    tmp := make([]int, 0, len(l)+len(r))
    for len(l) > 0 || len(r) > 0 {
        if len(l) == 0 {
            return append(tmp, r...)
        }
        if len(r) == 0 {
            return append(tmp, l...)
        }
        if l[0] <= r[0] {
            tmp = append(tmp, l[0])
            l = l[1:]
        } else {
            tmp = append(tmp, r[0])
            r = r[1:]
        }
    }
    return tmp
}

func MergeSort(s []int) []int {
    if len(s) <= 1 {
        return s
    }

    n := len(s) / 2

    l := MergeSort(s[:n])
    r := MergeSort(s[n:])

    return merge(l, r)
}

func MergeSortSmart(s []int) []int {
    if len(s) <= 1 {
        return s
    }

    n := len(s) / 2

    var wg sync.WaitGroup
    wg.Add(2)

    var l, r []int
    select {
    case semaphore <- pass{}:
        go func() {
            l = MergeSortSmart(s[:n])
            <-semaphore
            wg.Done()
        }()
    default:
        l = MergeSort(s[:n])
        wg.Done()
    }

    select {
    case semaphore <- pass{}:
        go func() {
            r = MergeSortSmart(s[n:])
            <-semaphore
            wg.Done()
        }()
    default:
        r = MergeSort(s[n:])
        wg.Done()
    }

    wg.Wait()
    return merge(l, r)
}

func MergeSortSmartBug(s []int) []int {
    if len(s) <= 1 {
        return s
    }

    n := len(s) / 2

    var wg sync.WaitGroup
    wg.Add(2)

    l := mergeSmart(s[:n], &wg)
    r := mergeSmart(s[n:], &wg)

    wg.Wait()
    return merge(l, r)
}

func mergeSmart(s []int, wg *sync.WaitGroup) []int {
    var tmp []int
    select {
    case semaphore <- pass{}:
        go func() {
            tmp = MergeSortSmartBug(s)
            <-semaphore
            wg.Done()
        }()
    default:
        tmp = MergeSort(s)
        wg.Done()
    }
    return tmp
}

为什么Bug 版本返回一个空切片?如何在不进行两次选择的情况下重构 Smart 版本?

抱歉,我无法在较小的示例中重现此行为。

【问题讨论】:

    标签: go channel goroutine


    【解决方案1】:

    我实现了这两个建议(通过引用传递切片和使用通道)并且(工作!)结果在这里:https://play.golang.org/p/DcDC_-NjjAH

    package main
    
    import (
        "fmt"
        "math/rand"
        "runtime"
        "sync"
    )
    
    type pass struct{}
    
    var semaphore = make(chan pass, runtime.NumCPU())
    
    func main() {
        rand.Seed(10)
        s := make([]int, 16)
        for i := 0; i < 16; i++ {
            s[i] = int(rand.Int31n(1000))
        }
    
        fmt.Println(s)
        fmt.Println(MergeSort(s))
        fmt.Println(MergeSortSmart(s))
        fmt.Println(MergeSortSmartPointer(s))
        fmt.Println(MergeSortSmartChan(s))
    }
    
    func merge(l, r []int) []int {
        tmp := make([]int, 0, len(l)+len(r))
        for len(l) > 0 || len(r) > 0 {
            if len(l) == 0 {
                return append(tmp, r...)
            }
            if len(r) == 0 {
                return append(tmp, l...)
            }
            if l[0] <= r[0] {
                tmp = append(tmp, l[0])
                l = l[1:]
            } else {
                tmp = append(tmp, r[0])
                r = r[1:]
            }
        }
        return tmp
    }
    
    func MergeSort(s []int) []int {
        if len(s) <= 1 {
            return s
        }
    
        n := len(s) / 2
    
        l := MergeSort(s[:n])
        r := MergeSort(s[n:])
    
        return merge(l, r)
    }
    
    func MergeSortSmart(s []int) []int {
        if len(s) <= 1 {
            return s
        }
    
        n := len(s) / 2
    
        var wg sync.WaitGroup
        wg.Add(2)
    
        var l, r []int
        select {
        case semaphore <- pass{}:
            go func() {
                l = MergeSortSmart(s[:n])
                <-semaphore
                wg.Done()
            }()
        default:
            l = MergeSort(s[:n])
            wg.Done()
        }
    
        select {
        case semaphore <- pass{}:
            go func() {
                r = MergeSortSmart(s[n:])
                <-semaphore
                wg.Done()
            }()
        default:
            r = MergeSort(s[n:])
            wg.Done()
        }
    
        wg.Wait()
        return merge(l, r)
    }
    
    func MergeSortSmartPointer(s []int) []int {
        if len(s) <= 1 {
            return s
        }
        n := len(s) / 2
        var l, r []int
    
        var wg sync.WaitGroup
        wg.Add(2)
    
        mergeSmartPointer(&l, s[:n], &wg)
        mergeSmartPointer(&r, s[n:], &wg)
    
        wg.Wait()
        return merge(l, r)
    }
    
    func mergeSmartPointer(tmp *[]int, s []int, wg *sync.WaitGroup) {
        select {
        case semaphore <- pass{}:
            go func() {
                *tmp = MergeSortSmartPointer(s)
                <-semaphore
                wg.Done()
            }()
        default:
            *tmp = MergeSort(s)
            wg.Done()
        }
    }
    
    func MergeSortSmartChan(s []int) []int {
        if len(s) <= 1 {
            return s
        }
        n := len(s) / 2
    
        lchan := make(chan []int)
        rchan := make(chan []int)
    
        go mergeSmartChan(s[:n], lchan)
        go mergeSmartChan(s[n:], rchan)
    
        l := <-lchan
        r := <-rchan
    
        return merge(l, r)
    }
    
    func mergeSmartChan(s []int, c chan []int) {
        select {
        case semaphore <- pass{}:
            go func() {
                c <- MergeSortSmartChan(s)
                <-semaphore
            }()
        default:
            c <- MergeSort(s)
        }
    }
    

    我 100% 理解我做错了什么,谢谢! 为了将来参考,这里是对 100,000 个元素进行排序的基准:

    $ go test -bench=.
    goos: linux
    goarch: amd64
    cpu: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz
    BenchmarkMergeSort-8                      97      12230309 ns/op
    BenchmarkMergeSortSmart-8                181       7209844 ns/op
    BenchmarkMergeSortSmartPointer-8         163       7483136 ns/op
    BenchmarkMergeSortSmartChan-8            156       8149585 ns/op
    

    【讨论】:

      【解决方案2】:

      问题不在于 WaitGroup 本身。它与您的并发处理有关。您的 mergeSmart 函数午餐 go 例程并返回 tmp 变量,而无需等待 go 例程完成。 您可能想尝试更像这样的模式:

      leftchan := make(chan []int)
      rightchan := make(chan []int)
      go mergeSmart(s[:n], leftchan)
      go mergeSmart(s[n:], rightchan)
      l := <-leftchan
      r := <-rightchan
      

      如果顺序无关紧要,您也可以使用单一渠道。

      【讨论】:

        【解决方案3】:

        查看mergeSmart 函数。当 select 进入第一种情况时,启动 goroutine 并立即返回 tmp (这是一个空数组)。在这种情况下,没有办法获得正确的值。 (请参阅此处的高级调试打印https://play.golang.org/p/IedaY3muso2) 也许传递通过引用预先分配的数组?

        【讨论】:

          【解决方案4】:

          mergeSmart 不会在 wg 上等待,因此它会返回一个尚未收到值的 tmp。您可以通过将目标切片的引用传递给函数来修复它,而不是返回切片。

          【讨论】:

            猜你喜欢
            • 2015-06-26
            • 1970-01-01
            • 2018-09-03
            • 2017-07-06
            • 2010-09-25
            • 1970-01-01
            • 2019-01-27
            相关资源
            最近更新 更多