Spark_Flink_Hadoop

go语言之goroutine

2020-01-29  本文已影响0人  kason_zhang
欢迎学习交流

goroutine

当go 语句执行的时候,其中的go函数会被单独放到一个goroutine中,在这之后该go函数的执行会独立于当前goroutine运行,一般情况下位于go语句后面的那些语句并不会等待前者的go 函数被执行完成才开始执行,甚至在该go 函数执行之前,运行时系统已经开始执行后面的语句了,所以go函数的并行执行,谁先谁后并不确定,看个例子:

package main

import (
    "fmt"
)

func main() {
    goroutineTest1()
    //runtime.Gosched()
        //time.Sleep(100 * time.Millisecond)
    fmt.Println("over")
    
}

// goroutine 执行的时间是不确定的,可早可晚
func goroutineTest1()  {
    go func() {
        fmt.Println("Hello, go routine")
    }()

}

这个例子最终没有输出Hello, go routine, 为什么这样呢? 就是上面那句话 go函数的执行谁先谁后并不确定, 这时候很大情况是main那个goroutine已经执行完了,那个打印的goroutine还来不及执行程序就结束了,
那如何让其打印呢? 加个sleep挂起主goroutine 或者使用runtime.Gosched() 即可让出cpu时间片,挂起当前goroutine让其他goroutine去执行。
下面在看一个例子,打印切片中数据

package main

import (
    "fmt"
    "time"
)

func main() {
    goroutineTest2()
    //runtime.Gosched()
    fmt.Println("over")
    //time.Sleep(100 * time.Millisecond)
}
func goroutineTest2()  {

    slices := []string{"hello","world","go","java","c++"}
    for i,v:= range slices {
        go func() {
            fmt.Printf("index=%d,value=%v\n", i, v)
        }()
    }
    time.Sleep(1000*time.Millisecond)
}
image

为什么全是输出index=4,value=c++呢?
因为goroutine的执行时间是不确定的,可能主函数的for循环已经执行完毕了, 但是goroutine还没开始执行,那么for循环执行完毕的时候i就变为了4,value就是c++,之后就可以一次执行了goroutine,所有输出全是index=4,value=c++, 那这种情况如果要解决的话需要怎么办呢?
值传递到匿名函数中去(注意是值传递也就是copy不能是地址传递,如果是地址传递最终还是有可能全部打印最后的值的)

package main

import (
    "fmt"
    "time"
)

func main() {
    goroutineTest3()
    //runtime.Gosched()
    fmt.Println("over")
    //time.Sleep(100 * time.Millisecond)
}

func goroutineTest3()  {

    slices := []string{"hello","world","go","java","c++"}
    for i,v:= range slices {
        go func(i int,v string) {
            fmt.Printf("index=%d,value=%v\n", i, v)
        }(i,v)
    }
    time.Sleep(1000*time.Millisecond)
}
image

多执行几次会发现结果是无序的, 那是因为无法确定goroutine的执行时机,但是k,v索引以及value值是正确无误的。
上述代码是对匿名函数进行了值传递,也就是进行了copy,这时候与原值就独立了的。
这里面一个注意点就是值类型传递,而不能是引用类型传递,那么这里就简单解释下go语言里面这两个的区别。
两者的主要区别体现在赋值操作和函数传参时。

值类型

基本类型加数组array属于值类型。即bool,byte,rune,int,int8,int16,int32,int64,float32,floast64,array这些类型都
声明一个值类型变量时,编译器会在栈中分配一个空间,空间里存储的就是该变量的值。赋值和传参时是用法是复制这个对象的值给别人。

引用类型

切片,映射map,函数,方法,通道,接口,string都是引用类型。
string类型很特殊,string的底层实现的是引用类型struct String { byte* str; intgo len; }; 但是因为string不允许修改,每次操作string只能生成新的对象,所以在看起来使用时像值类型。
引用类型的实例分配在堆上,新建一个引用类型的实例时,得到的变量值是存储该实例的内存地址或着说是第一个字所在的位置的内存地址,这个内存地址被称之为指针。引用类型可以简单的理解为指针类型,它们都是通过make完成初始化 。
赋值和传参时是传的指针,即值的地址。
nil只能赋值给引用类型(除string外)和error类型.
那这里为了体现goroutine传参如果传入引用类型的问题,这块举例如下

package main

import (
    "fmt"
    "time"
)

func main() {
    goroutineTest4()
    //runtime.Gosched()
    fmt.Println("over")
    //time.Sleep(100 * time.Millisecond)
}

func goroutineTest4()  {

    maps := map[string]int{"k":1,"b":2}

    for i:=0; i < 5; i++ {
        maps[string(i)] = i
        go func(maps map[string]int) {
            fmt.Printf("%v",maps)
        }(maps)
    }
    time.Sleep(1000*time.Millisecond)
}
image

打印的maps并不是一个一个的增加元素进去, 说明引用传递最终输出的还是最终的那个map。

上一篇 下一篇

猜你喜欢

热点阅读