仓库源文站点原文


title: Go创建Goroutine时显式调用时的坑 toc: true cover: 'https://img.paulzzh.com/touhou/random?23' date: 2021-01-21 11:27:42 categories: Golang tags: [Golang, Goroutine]

description: 在Go中可以直接通过go关键字直接创建一个goroutine并在子goroutine中直接调用函数;但是有时候由于调用的方式不同会存在一些问题;

在Go中可以直接通过go关键字直接创建一个goroutine并在子goroutine中直接调用函数;

但是有时候由于调用的方式不同会存在一些问题;

源代码:

<br/>

<!--more-->

Go创建Goroutine时显式调用时的坑

Goroutine调用概述

对于go关键字创建新goroutine并调用函数的方式有两种:

func main() {
    x := 1
    // 隐式传参调用
    // 此时传入的是x的“引用值”,即两个x指向的是同一个内存地址,在子routine中修改的值,会改变外部的x!
    go func() {
        fmt.Println(x)
    }()

    // 直接传参调用
    // 此时为值传递,内部的x不会影响外部的x;
    go func(x int) {
        fmt.Println(x)
    }(x)
}

两者的区别在于:

另外,需要注意的:<font color="#f00">**显式的传参,在传参时就必须将参数计算好,这一点和defer函数是相同的!**</font>

例如:

func main() {
    wg := sync.WaitGroup{}
    wg.Add(2)

    x := 1
    // 隐式传参调用
    // 此时传入的是x的“引用值”,即两个x指向的是同一个内存地址,在子routine中修改的值,会改变外部的x!
    go func() {
        fmt.Printf("Implicit invoke: %d\n", x)
        wg.Done()
    }()

    // 直接传参调用
    // 此时为值传递,内部的x不会影响外部的x;
    go func(x int) {
        fmt.Printf("Direct invoke: %d\n", x)
        wg.Done()
    }(x)

    x = 3

    wg.Wait()
}

上面的函数大概率输出为:

Direct invoke: 1
Implicit invoke: 3

这是因为,在直接传参调用时,x的值还未被修改(仍然是1)并且已经被确定,而隐式传参调用会根据外部x值的改变而改变;

之所以说是大概率是因为,一般情况下,隐式传参调用的goroutine执行速度还是比main中执行至x=3语句要慢的,所以,大概率会先执行x=3修改x的值,随后才会执行隐式传参调用!

<br/>

一道关于 Goroutine 的题

下面的代码输出什么呢?

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan int)
    go fmt.Println(<-ch1)
    ch1 <- 5
    time.Sleep(1 * time.Second)
}

以上代码输出什么?(单选)

如果你耐心看了上面的讲解,可以很容易知道正确答案是:C;

因为:

在上方创建Goroutine进行调用时,实际上是显式传参!

所以,上方的代码其实类似于:

func main() {
    ch1 := make(chan int)
    x := <-ch1
    go fmt.Println(x)
    ch1 <- 5
    time.Sleep(1 * time.Second)
}

此时x := <-ch1会阻塞main函数,而ch1 <- 5也是在main函数中调用的,所以会被阻塞,最终造成死锁!

<br/>

附录

Goroutine题目来源:

源代码:

<br/>