Go语言之旅练习 #1:并发和go关键字

3

我正在学习《Go语言之旅》,并且已经编辑了大部分课程以确保我完全理解它们。我有一个关于https://tour.golang.org/concurrency/1的问题,这个问题与并发相关。

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

离开main不变会产生随机的hello和world的排序,因为线程每次运行程序时执行的顺序都不同。我有两个问题:
  1. 如果我从包含world的那一行中删除go并添加到包含hello的那一行中,那么world会打印5次,而hello则完全没有打印。这是为什么?
  2. 如果我在两行前面都加上go,什么也不会打印。这是为什么?
我对使用C++进行并发编程有一些经验(尽管是一段时间以前的经验),并且最近在Python方面有一些经验,但是我的总体并发编程经验可以描述为初学者级别。
谢谢!
2个回答

6
程序在你看到结果之前就终止了。
通过添加一条语句确保其他例程完成后,您可以修复此问题。
来自Go - 并发:

使用goroutine时,我们立即返回到下一行,而不等待函数完成

他们给出了一个代码示例:
package main

import "fmt"

func f(n int) {
  for i := 0; i < 10; i++ {
    fmt.Println(n, ":", i)
  }
}

func main() {
  go f(0)
  var input string
  fmt.Scanln(&input)
}

关于上述代码:

这就是为什么包含了Scanln函数的调用;如果没有它,程序将在打印所有数字之前退出


谢谢你的回答和附带文献!等待Go协程完成的首选方式是什么?或者如果你发现自己处于这种情况,是否意味着你应该重新设计代码以避免这种情况?我还在练习中发布了另一个问题,如果你感到慷慨的话 :) https://stackoverflow.com/questions/46147914/explanation-of-channels-from-tour-of-go-webcrawler-exercise - leif
@leif 从我的经验来看,通常没有必要重新设计以避免这种情况,尽管这只是个人经验。一般来说,您的应用程序将包括处理任务并将结果委托回运行直到应用程序实际需要终止的线程的例程。如果您真的需要这种行为,您可以使用Gosched来让主线程暂停,从而允许您的例程进行处理。查看此帖子 - Dioxin

0
如果我从say("world")这一行中删除go关键字,并将其添加到say("hello")这一行中,那么world会被打印5次,而hello则根本不会被打印。为什么会这样呢?
如果我在两行代码前面都加上go,那么就什么也不会打印出来。为什么会这样呢?
在这两种情况下,都存在同步操作的问题。这导致程序在所有启动的工作处理完成之前就从main返回。规范指出:当该[main]函数调用返回时,程序退出。它不等待其他(非主)goroutine完成。

当您使用go关键字时,运行时会将函数调用的执行作为独立的并发线程控制。您需要使用同步语言原语来重新同步在调用main后遵循操作退出顺序与剩余异步作业。

该语言提供了channel或通过sync包以其他方式提供WaitGroup,以帮助您实现该行为。

例如

package main

import (
    "fmt"
    "time"
    "sync"
)

var wg sync.WaitGroup

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
        wg.Done()
    }
}

func main() {
    wg.Add(5)
    say("world")
    wg.Add(5)
    say("hello")

    wg.Wait()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接