Go语言进阶(二)Goroutine

进程和线程

操作系统会为该应用程序创建一个进程。作为一个应用程序,它像一个为所有资源而运行的容器。这些资源包括内存地址空间、文件句柄、设备和线程。

线程是操作系统调度的一种执行路径,用于在处理器执行我们在函数中编写的代码。一个进程从一个线程开始,即主线程,当该线程终止时,进程终止。这是因为主线程是应用程序的原点。然后,主线程可以依次启动更多的线程,而这些线程可以启动更多的线程。

无论线程属于哪个进程,操作系统都会安排线程在可用处理器上运行。每个操作系统都有自己的算法来做出这些决定。

协程和并行

Go 语言层面支持的 go 关键字,可以快速的让一个函数创建为 goroutine,我们可以认为 main 函数就是作为 goroutine 执行的。操作系统调度线程在可用处理器上运行,Go运行时调度 goroutine 在绑定到单个操作系统线程的逻辑处理器中运行(P)。即使使用这个单一的逻辑处理器和操作系统线程,也可以调度数十万 goroutine 以惊人的效率和性能并发运行。

并行和并发的区别

Concurrency is not Parallelism. (并发不是不行)

并发不是并行。

并行是指两个或多个线程同时在不同的处理器执行代码。

如果将运行时配置为使用多个逻辑处理器,则调度程序将在这些逻辑处理器之间分配 goroutine,这将导致 goroutine 在不同的操作系统线程上运行。

但是,要获得真正的并行性,您需要在具有多个物理处理器的计算机上运行程序。否则,goroutine 将针对单个物理处理器并发运行,即使 Go 运行时使用多个逻辑处理器。

打个不恰当的比喻

  • 串行:我先做饭,再吃饭。
  • 并发:去接女儿放学的路上,顺便买瓶酱油,看到卖西瓜的再买个西瓜回来。
  • 并行:电话来了,一边打电话一边吃饭

Keep yourself busy or do the work yourself(脏活累活自己干)

案例1:单服务

Leave concurrency to the caller(让调用者自己决定是否需要并发)

  • 同步模型 将目录读取到一个 slice 中,然后返回整个切片,或者如果出现错误,则返回错误。这是同步调用的,ListDirectory 的调用方会阻塞,直到读取所有目录条目。根据目录的大小,这可能需要很长时间,并且可能会分配大量内存来构建目录条目名称的 slice

  • 异步模型 ListDirectory 返回一个 chan string,将通过该chan 传递目录。当通道关闭时,这表示不再有目录。由于在 ListDirectory 返回后发生通道的填充,ListDirectory 可能内部启动 goroutine 来填充通道。不知道执行完了,还是中间报错了。chan具有二义性。

可以考虑callback模型:可以更好的解决者问题,不需要西东goroutinechan

参考了filepath.WalkDir

如果函数启动 goroutine,则必须向调用方提供显式停止该goroutine 的方法。通常,将异步执行函数的决定权交给该函数的调用方通常更容易。

Never start a goroutine without knowning when it will stop(不要启动一个不知道何时才能停止的goroutine)

案例1:

没有任何东西发送给goroutine,代码会pendingval:=<-ch这里,导致协程永远无法被退出,导致了协程泄露。

案例2:search 函数是一个模拟实现,用于模拟长时间运行的操作,如数据库查询或 rpc 调用。在本例中,硬编码为200ms。定义了一个名为 process 的函数,接受字符串参数,传递给 search。对于某些应用程序,顺序调用产生的延迟可能是不可接受的。

同步模型

异步模型

如果<-ctx.Done()先执行,并且ch不是一个buffer chan,那么就意味着没有人获取ch中的数据的话,ch将未必pending,导致goroutine无法退出。

解决方案:var ch = make(chan string, 1)

启动goroutine前先问自己两个问题:

  • 它什么时候会结束?
  • 有没有办法主动结束它。

回到启动服务的案例,当我们启动多个服务时如何规范地启动goroutine

Applicate Lifecycle

对于应用的服务的管理,一般会抽象一个 application lifecycle 的管理,方便服务的启动/停止等。

  • 应用信息
  • 服务的启动/停止
  • 处理信号
  • 服务注册

Incomplete Work(继续说泄露的案例)

一个简单的改造

但是这个demo还一个问题,如果goroutine执行时间超长,并且没有一个退出机制。那么我们的main函数将永远无法退出。

我们将引入context,继续改造

最终demo可以参考:https://gist.github.com/bllli/7bb6ba09a09fd283f88d47f3b6a1d5d7