WaitGroup在Go语言中相对于其他语言(如Python)是否是一种退步?

huangapple go评论91阅读模式
英文:

Is WaitGroup in golang a step backward compare to other languages like Python?

问题

我对golang非常陌生,最近在尝试使用goroutine,虽然并发运行任务很容易,但是我对golang使用WaitGroup来"加入线程"的方式感到有些惊讶。

据我所知,goroutine需要引用WaitGroup对象来调用Done()方法,这意味着我要么让goroutine接受一个WaitGroup对象,要么将WaitGroup对象作为全局变量传递给goroutine。

但是在其他语言如Python中,你可以调用thread.join(),"控制"部分位于线程代码之外。

就像我说的,我对golang非常陌生,不知道为什么它被设计成这样,能否有人对这个方面进行解释一下?

更新
我希望讨论的不是"goroutine vs 线程",毕竟它们都试图实现某种形式的"并发",我的问题更多是关于控制程序流程的。

英文:

I am very new to golang and I was trying out goroutine, while it's quite easy to run things concurrently, I am a bit surprised the way golang to "join the threads" using WaitGroup.

As far as I know, the goroutine needs to have reference to the WaitGroup object to call Done(), which means, I have to either make the goroutine to accept a WaitGroup object, or make WaitGroup object global to the goroutine.

But in other languages like Python, you call thread.join(), the "controlling" part sits outside of the thread code.

Like I said, I am very new to golang, I don't know why it was designed this way, could someone please shed some light on this aspect?

UPDATE:
I hope the argument is not based on 'Goroutine vs Thread', at the end of the day they both try to achieve (some kind of) 'concurrency', my question is more about controlling the program flow.

答案1

得分: 5

为什么要这样设计呢?

这个问题其实已经被Go语言团队解释过很多次了——为什么我们不能终止goroutine,为什么它们没有一个我们可以读取的ID,为什么我们不能像线程的Join一样显式地等待goroutine。

虽然解释过很多次,但我只找到了这个。基本上,作者们不希望你依赖于线程本地性——锁定特定的线程/ goroutine,仅为其提供本地存储等。当你没有任何方法知道你实际上是在哪个goroutine中运行时,你被迫以真正并发的方式设计你的应用程序。你的代码由真正独立的部分组成,它们并发地运行,而且它们不关心具体的细节。你不关心哪个goroutine执行你的代码,你不关心哪个操作系统线程正在运行你的代码。这就是通道、选择和其他原语的用武之地。它们帮助你以这样的方式构建应用程序。而且我相信这并不止于此。

英文:

> why it was designed this way

That's actually been explained many times by the golang team - why can't we kill goroutines, why doesn't they have an ID which we can read, why can't we wait for goroutine explicitly like with thread's Join.

It was explained multiple times but I could find only this. Basically, the authors didn't want you to depend on thread locality - to lock on a specific thread/goroutine, have a local storage only for it etc. When you don't have any means to know in which goroutine you're actually running you're forced to design your application in a truly concurent way. Your code is composed of truly independend pieces that run concurently and they don't care how exactly. You don't care which goroutine picks up your code, you don't care which OS thread is running your code. That's where channels, select and other primitives come in. They help you build your application in such a way. And I'm sure it doesn't stop there.

答案2

得分: 3

不,它只是做了不同的事情。它们甚至不能真正进行比较,因为WaitGroup本质上等待多个事物(并且在其生命周期中可以添加事物),而Python线程的join始终只等待一个事物。

话虽如此,Go的库更多地是为您提供执行更高级任务所需的基本工具,而Python的库则更多地采用“电池包含”的哲学。使用Go提供的工具,您可以创建一个行为类似于Python Thread的类型。这可能不是最好的使用Go的方式,但如果您愿意,您可以使用这些工具来实现。然而,标准库不会对此进行标准化。

英文:

No, it's just a different thing that does a different thing. They're not even really comparable, since a WaitGroup by its nature waits on multiple things (and can have things added to it during its lifetime) and a python thread's join always just waits on that one thing.

That said, Go's library is more about giving you the primitive things that you need to do more advanced things, while Python's has more of a "batteries included" philosophy. Using what Go gives you, you could create a type that acts quite a bit like a python Thread. It's probably not the best way to make use of Go, but you're given the tools to do it if you want. However the standard library isn't going to standardize on such a thing.

答案3

得分: 1

hobbs和creker的回答特别好,但我觉得还有更多需要说的。

有一个非常常见的观念,即WaitGroup是管理多个goroutine的唯一方法-它确实在许多情况下被广泛使用,甚至成为惯用法。而且你知道吗?在只是等待一堆之前启动的线程/ goroutine 时,能够调用 thread.join() 可能确实优于处理 WaitGroup。

但是,Go的并发模型远不止于此。

Goroutine被专门设计为*不具有所有权、层次结构或句柄的概念。它们是独立的、平等的,并负责结束自己的执行。这个特性,再加上强大的并发原语,使得Go的模型几乎具有无与伦比的灵活性。

因此,如果你发现自己几乎每次使用goroutine时都在使用WaitGroup,那么你可能没有充分利用并发在建模和构造程序方面-更有可能的是你只是在使用goroutine来并行计算。

更直接地回答你的问题,与 thread.join() 等东西相比,WaitGroup相对较原始,但是在Go的并发模型中,原始的、底层的构建模块更加有用。毕竟,goroutine不是线程,也不是用来完全相同的方式使用的。

英文:

The answers from hobbs and creker especially are excellent, but I feel there's more to be said.

There's a very common notion that WaitGroup is the way to manage multiple goroutines - it certainly is commonly used and even idiomatic in a number of situations. And you know what? Being able to call thread.join() may indeed be superior to dealing with WaitGroups when just waiting on a bunch of threads/goroutines launched earlier.

But there's so much more to Go's concurrency model than that.

Goroutines were specifically designed to not have concepts of ownership or hierarchy or handles. They are independent, equal and responsible for ending their own execution. That, combined with strong concurrency primitives, gives Go's model almost unparalleled flexibility.

Therefore, if you find yourself using WaitGroups almost every time you're using goroutines, you're probably not taking advantage of concurrency in modeling and structuring your programs - it's more likely you're just using goroutines to parallelize computation.

To answer your question more directly, WaitGroups are rather primitive compared to stuff like thread.join(), but primitive, low-level building blocks are much more useful with Go's concurrency model. After all, goroutines are not threads, and they're not meant to be used exactly the same way.

答案4

得分: 0

据我所知,goroutine需要引用WaitGroup对象来调用Done()方法,这意味着我要么让goroutine接受一个WaitGroup对象,要么将WaitGroup对象作为全局变量给goroutine使用。

我不同意将WaitGroup与其他语言相比看作是一种倒退。我真的很喜欢它的简洁性。然而,我同意传递WaitGroup对象意味着并发逻辑会与业务逻辑混合在一起,我不喜欢这种情况。

所以我想出了这个通用函数来解决这个问题:

// Parallelize函数并行执行函数调用
func Parallelize(functions ...func()) {
    var waitGroup sync.WaitGroup
    waitGroup.Add(len(functions))

    defer waitGroup.Wait()

    for _, function := range functions {
        go func(copy func()) {
            defer waitGroup.Done()
            copy()
        }(function)
    }
}

这是一个示例:

func1 := func() {
    for char := 'a'; char < 'a' + 3; char++ {
        fmt.Printf("%c ", char)
    }
}

func2 := func() {
    for number := 1; number < 4; number++ {
        fmt.Printf("%d ", number)
    }
}

Parallelize(func1, func2)  // 输出:a 1 b 2 c 3

如果你想使用它,你可以在这里找到:https://github.com/shomali11/util

英文:

> As far as I know, the goroutine needs to have reference to the WaitGroup object to call Done(), which means, I have to either make the goroutine to accept a WaitGroup object, or make WaitGroup object global to the goroutine.

I disagree that WaitGroup is a step backward when compared to other languages. I really like its simplicity. However, I do agree that passing the WaitGroup object around would mean that the concurrency logic would be mixed with your business logic and I did not like that.

So I came up with this generic function to solve this problem for me:

// Parallelize parallelizes the function calls
func Parallelize(functions ...func()) {
    var waitGroup sync.WaitGroup
    waitGroup.Add(len(functions))

	defer waitGroup.Wait()

	for _, function := range functions {
    	go func(copy func()) {
	    	defer waitGroup.Done()
		    copy()
	    }(function)
    }
}

Here is an example:

func1 := func() {
        for char := &#39;a&#39;; char &lt; &#39;a&#39; + 3; char++ {
            fmt.Printf(&quot;%c &quot;, char)
        }
}

func2 := func() {
        for number := 1; number &lt; 4; number++ {
            fmt.Printf(&quot;%d &quot;, number)
        }
}

Parallelize(func1, func2)  // a 1 b 2 c 3

If you would like to use it, you can find it here https://github.com/shomali11/util

huangapple
  • 本文由 发表于 2016年3月1日 03:59:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/35708494.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定