一.语言进阶
1.1 Goroutine(协程)
并发:多线程程序在一个核的cpu上运行
并行:多线程程序在多个核的cpu上运行
协程:用户态,轻量级线程,栈KB级别
线程:内核态,线程跑多个协程,栈MB级别
调用函数或者方法时,在前面加上关键字 go,可以让一个新的 Go 协程并发地运行。 一个go func(){}()开启一个子协程,for + go func(){}()开启多个子协程
func(){}是一个匿名函数
for i := 0; i < 5; i++ { go func(j int) { fmt.Println(j) }(i) }
如果你只看到了四个值而不是五个,这可能是因为主 Goroutine 在启动了五个 Goroutine 后就终止了,而其中一个或多个 Goroutine 没有足够的时间来完成打印操作。这是一个并发执行的情况,具体的输出结果可能因为 Goroutine 的调度和执行时间而有所不同。
主 Goroutine 与其他 Goroutine 之间没有本质上的区别。它们都是并发执行的独立任务,具有相同的特性和行为。主 Goroutine 只是在程序函数开始时自动创建的一个特殊 Goroutine,用于启动整个程序的执行。如果希望运行其他 Go 协程,Go 主协程必须继续运行着。如果 Go 主协程终止,则程序终止,于是其他 Go 协程也不会继续运行。
当我们提到主协程时,通常是指程序的入口协程,但在更复杂的并发程序中,可能会有多个并发执行的协程,它们在某种程度上都可以被称为主协程。这个称呼是相对而言的,取决于上下文和具体的程序结构
对于协程中的defer,C++语言里的析构函数(Destructor)可以起类似的作用,C++语言机制担保在对象被销毁前一定会执行析构函数中的代码。C++中的析构函数析构的是对象,Go中的defer析构的是函数。
1.2 CSP(Communicating Sequential Processes)
提倡通过通信共享内存而不是通过共享内存而实现通信
1.3 Channel(通道,信道)
make(chan int) 无缓冲通道(同步通道)
make(chan int,2) 有缓冲通道(第二个参数是缓冲大小)
(make()函数专门用于创建切片、映射和通道)
无缓冲的通道是指在发送数据和接收数据时,发送方和接收方必须同时准备好。也就是说,当发送方发送数据时,如果没有接收方正在等待接收数据,发送方将会被阻塞,直到有接收方准备好接收数据。同样地,当接收方准备好接收数据时,如果没有发送方正在发送数据,接收方也会被阻塞,直到有发送方准备好发送数据。
有缓冲的通道则允许在发送数据和接收数据时存在一定的缓冲空间。发送方可以一直发送数据,直到通道的缓冲区满了为止。而接收方可以一直接收数据,直到通道的缓冲区为空为止。当缓冲区已满时,发送方将会被阻塞,直到有接收方从通道中取出数据,腾出缓冲空间。同样地,当缓冲区为空时,接收方将会被阻塞,直到有发送方往通道中发送数据。
因此,无缓冲的通道保证了发送和接收的同步,而有缓冲的通道可以提供一定的异步性能。
通道发送操作:在表达式的左侧,<- 用于将一个值发送到通道中。例如,channel <- value 表示将 value 发送到名为 channel 的通道中。
通道接收操作:在表达式的右侧,<- 用于从通道中接收一个值。例如,value := <-channel 表示从名为 channel 的通道中接收一个值,并将其赋值给 value。
1.4 用time.Sleep(time.Second)
具体来说,time.Sleep()函数会暂停当前协程的执行,让其进入休眠状态,并阻塞该协程的执行一段时间。它接受一个time.Duration类型的参数,表示休眠的时间长度。
在time.Sleep(time.Second)中,time.Second表示一秒钟的时间长度。因此,该函数调用会使当前协程休眠一秒钟。
time.Sleep()函数常用于以下场景:
1.在并发程序中,通过休眠一段时间来模拟并发执行的协程之间的时间间隔。
2.在需要控制程序执行速度的情况下,可以使用time.Sleep()来降低程序的执行速度。
1.5 WaitGroup
使用WaitGroup需要以下步骤:
1.创建一个WaitGroup对象:var wg sync.WaitGroup(Sync 是 "synchronization" 的缩写)
2.在每个需要等待的协程开始执行前,调用wg.Add(1)来增加WaitGroup的计数器。(n个协程就wg.Add(n))
3.在每个协程的任务完成后,调用wg.Done()来减少WaitGroup的计数器。
4.在主协程(或其他需要等待的协程)中,调用wg.Wait()来等待所有协程完成任务。
使用 defer wg.Done():在协程的任务代码中使用defer wg.Done()语句,会确保在任务执行结束时自动调用wg.Done()。这样可以避免遗漏调用wg.Done()的情况,即使在任务中发生了异常或提前返回,也能保证计数器被正确减少。
直接使用 wg.Done():在协程的任务代码中直接调用wg.Done(),需要手动确保在任务的适当位置调用wg.Done()。这样需要开发者自己保证在任务执行结束时调用wg.Done(),否则计数器不会被正确减少,会导致wg.Wait()一直阻塞。
使用defer wg.Done()可以确保在任务结束时自动调用wg.Done(),而直接调用wg.Done()需要手动保证在适当的位置调用。
二.依赖管理
2.1 Go依赖管理演进
GOPATH->GO Vendor->GO Module
2.2 依赖管理三要素
1.配置文件,描述依赖 go.mod
2.中心仓库管理依赖库 proxy
3.本地工具 go get/mod
2.3 依赖配置
2.3.1 依赖配置-go.mod
2.3.2 依赖配置-version
2.3.3 依赖配置-indirect
A->B->C
A->B 直接依赖
A->C 间接依赖
2.3.4 依赖配置-incompatible,依赖图