零基础学习Go的Day08| 青训营笔记

85 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

1.复习已学知识

  • 通过学习Go语言的实战案例,让我对Go语言的使用更加熟练
  • 通过Go实现猜字游戏学会了在Go中生成随机数,并且随机数需要通过设置种子的方式进行,并且er流获取键盘输入的数字,使用循环对此过程进行优化
  • 通过实现字典的案例,学会了伪造HTTP请求对服务器发起HTTP请求,并以结构体的方式进行传参,并在处理HTTP请求的时候使用合理的异常使用
  • 通过实现Socket5代理服务器,学会了在浏览器中使用插件实现代理,并且o中的协程的,使用"go"关键词去调用

2.观看Go语言进阶与依赖管理

  • 学习并发和并行的区别
  • 了解并发的概念
  • 学习并发和进程
  • 使用Goroutine
    与其他大部分语言提供的协程支持相同,Go 的 Goroutine 是用户态的,其协程栈占用仅有 KB 级别,十分节约系统资源;但不同的是,Goroutine 将协程和并发简化到了仅需一个 go 关键字即可完成,而不像其他语言的协程一样及其繁琐复杂。Goroutine 是有栈协程,而不是如 Kotlin 协程那样的无栈协程 看一个典型的 Goroutine 例子,来感受一下 Goroutine 的魅力:
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")
    time.Sleep(time.Second)
}

首先,要注意到,即使你在你的程序中不使用任何一个 go 关键字,也依然存在一个协程在运行:这个协程就是 main 函数自己。

上文的代码中,定义了一个 say 函数,接受一个字符串形参,作用是每隔 100 毫秒打印一次传入的字符串,重复 5 次;接下来,在 main 函数,调用了两次 say 函数,并传入不同的参数 "hello""world",最后,调用 time.Sleep() 函数令当前协程(也就是 main 函数的主协程)休眠 1 秒。与以往不同的是,第一个 say 函数前被标上了一个 go 关键字 —— 这意味着该函数将在一个新的 Goroutine 协程中运行
来看看输出结果

hello
world
world
hello
hello
world
world
hello
hello

是不是很意外?看起来 helloworld 两个字符串在以未知的顺序交替打印,看上去就好像有两个线程在同时打印字符串一样
协程陷阱:并发不是真正的并行
上述代码中的输出结果看上去就好像有两个线程在并行的打印字符串,但事实上,是这样吗?尝试注释掉 time.Sleep(100 * time.Millisecond),看看又会有什么结果

world
world
world
world
world
hello
hello
hello
hello
hello

打印结果看起来又和单线程一样了!这是怎么回事?

事实上,也正如我在上一节所说的,并发只是一种错觉,程序并没有真正的并行运行,也就是说,只有在一段程序有空闲时间的时候,另一端程序才有机会抢过执行权,执行自己

因此,在上例中,由于我们并没有调用 time.Sleep 来告知其他程序有空闲时间可以给你执行,因此其他程序只能等待这个协程内的程序执行完成,然后才有机会执行自己。因此,我们得到了一段顺序执行的打印输出。

协程陷阱:提前结束的主线程(协程)

你可能会注意到,在上述代码的 main 中,有一个 time.Sleep(time.Second) 函数将主协程休眠了 1 秒,这有什么用?让我们试试注释掉这段代码(以及上文中的 time.Sleep(100 * time.Millisecond)),看看输出结果:

hello
hello
hello
hello
hello

只有 hello,没有 world!这是怎么回事。

事实上,主协程是一个特殊的协程,如果主协程执行完毕,那么其他子协程也会停下手上的活,直接退出。由于我们注释掉了 time.Sleep(time.Second),那么在 say("hello") 函数执行完毕后,程序便会直接退出,不会再等待接着执行的 say("world")

因此,当我们有多个子协程执行时,应该等待这些协程全部执行完毕后,再结束主协程

当然,要想做到这一点,绝不是用 time.Sleep() 这样的函数,因为我们无法获知其他协程的执行时间。实际上 Go 语言标准库为我们提供了更好的解决方案,那就是 WaitGroup