Goroutine初步|青训营

82 阅读3分钟

Goroutine

说goroutine之前先来说一下协程线程进程

协程线程进程
用户态,轻量级线程内核态启动程序的启动实例
比线程更轻量级的微线程,一个线程可以有多个协程线程是操作系统能够进行运算调度的最小单位,线程被包含在进程之中,是进程中的实际运作单位进程是资源分配的基本单位

并发与并行

  • 并发是指多线程程序在一个核的cpu上运行(许多蚂蚁在一根树枝上爬)

  • 并行是多线程程序在多个核的cpu上运行(许多树枝上有许多蚂蚁在爬)


1.什么是goroutine

Go实现了两种并发形式

  • 第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。
  • 第二种是Go语言特有的,也是Go语言推荐的:CSP并发模型(通过通信来共享内存)。

Goroutine是与其他函数或方法同时运行的函数或方法,可以被认为是轻量级线程,区别于进程,线程,协程。

2.goroutine在线程上的优势

  • goroutine(堆栈大小可以根据应用程序需求改变)比创建线程(堆栈大小固定)成本小

  • goroutine被多路复用到较少的OS线程。一个程序中可能只有一个线程和上千个goroutine。

  • 使用goroutine访问共享内存,通过设计的通道可以防止竞态条件发生。通道可以被认为是goroutine通信的管道。

3.如何使用Goroutine

在函数或方法调用前面加上go关键字

一个goroutine必定对应一个函数

func hello(){
fmt.Println("hello")
}

func main(){
go hello() //启动一个goroutine
fmt.Println("gee")
}

运行结果为

gee

在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine也就会一同结束。所以输出结果只是一个gee

进行修改:

func hello(){
    fmt.Println("hello")
}

func main(){
    go hello() //启动一个goroutine
    time.Sleep(1*time.Second)
    fmt.Println("gee")
}

结果为

hello
gee

main goroutine因为time.Sleep()函数等待新的goroutine hello创建并执行后再执行main中的Println,最后整个main goroutine结束。 如果把time.Sleep()函数放置到最后,则输出顺序改变——先执行main() goroutine,输出gee,然后延时,该延时足以让hello goroutine运行,输出hello,最后延时结束,main() goroutine结束。(注意两个goroutine是并发的。)

3.1 启动多个goroutine

举一个例子

func main(){
    for i:=0;i<5;i++{
    go godsay()
   }
    time.Sleep(4*time.Second)
}

func godsay(){
    time.Sleep(3*time.Second)
    fmt.Println("...god...")
}

结果为

...god...
...god...
...god...
...god...
...god...

main函数启动了5个godsaygoroutine,让其休眠3秒后打印。

再进行修改

func main(){
    for i:=0;i<5;i++{
    go sleepy(i)
    }
    time.Sleep(4*time.Second)
}

func sleepy(id int){
    time.Sleep(3*time.Second)
    fmt.Println("...god...",id,"stepback")
}

结果为

...god... 3 stepback
...god... 2 stepback
...god... 4 stepback
...god... 0 stepback
...god... 1 stepback

可以看到,输出顺序是随机的,这是因为goroutine是并发执行的,而goroutine的调度是随机的

再运行程序的时候可以明显感受到等待时间并不按照预期,我们通过Go通道来控制goroutine结束时间。