p.s.这是萌新自己自学总结的笔记,如果想学习得更透彻的话还是请去看大佬的讲解
目录
- goroutine并发
- Channel-线程间通信工具
- [Go Module](#Go Module)
goroutine并发
goroutine 是 Go 语言中的轻量级线程,由 Go 运行时(runtime)管理,允许在程序中并发地执行函数。
主要特点:
轻量级:
相比操作系统线程,goroutine 开销极小(初始栈大小约 2KB,可按需增长/收缩)
可轻松创建数十万甚至上百万个 goroutine
并发模型:
采用 CSP(Communicating Sequential Processes)并发模型
通过 channel 进行 goroutine 间的通信
实现"不要通过共享内存来通信,而要通过通信来共享内存"
调度机制:
由 Go 运行时调度,采用 M:N 调度模型
多个 goroutine 在少量操作系统线程上复用
调度是协作式的,goroutine 会在特定点(如 I/O 操作、channel 操作)主动让出 CPU
线程在切换中会有时间成本,进程/线程的数量越多,切换成本就越大,也就越浪费并且多线程的同步竞争问题(如锁,资源竞争冲突)会导致CPU的高消耗调度CPU与高内存占用
缺点:一个协程阻塞会导致其他协程一直等待
实现
go
package main
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("new Goroutine : i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
//主goroutine
func main() {
//创建一个go程 去执行newTask() 流程
go newTask()//次goroutine
i := 0
for {
i++
fmt.Printf("main goroutine: i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
go
package main
import (
"fmt"
"time"
)
func main() {
/*
//用go创建承载一个形参为空,返回值为空的一个函数
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
//退出当前goroutine
runtime.Goexit()
fmt.Println("B")
}()
fmt.Println("A")
}()
//死循环
for {
time.Sleep(1 * time.Second)
}
*/
go func(a int, b int) bool {
fmt.Println("a = ", a, " b = ", b)
return true
}(10, 20)
//死循环
for {
time.Sleep(1 * time.Second)
}
}
Channel-线程间通信工具
无缓冲Channel,可以实现同步线程的效果
go
package main
import "fmt"
func main() {
//定义一个channel
c := make(chan int)
go func() {
defer fmt.Println("goroutine结束")
fmt.Println("goroutine 正在运行...")
c <- 666 //将666 发送给c
}()
num := <-c //从c中接受数据,并赋值给num
fmt.Println("num = ", num)
fmt.Println("main goroutine 结束...")
}
而有缓冲区的Channel可以使两个线程分别进行传递与接收操作,即异步通信
go
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3) //带有缓冲的channel
fmt.Println("len(c) = ", len(c), ", cap(c)", cap(c))
go func() {
defer fmt.Println("子go程结束")
for i := 0; i < 3; i++ {
c <- i
fmt.Println("子go程正在运行,发送的元素=", i, " len(c)=", len(c), ", cap(c)=", cap(c))
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ {
num := <-c //从c中接收数据,并赋值给num
fmt.Println("num = ", num)
}
fmt.Println("main 结束")
}
输出:
root@85db594393fb:/app# go run main.go
len(c) = 0 , cap(c) 3
子go程正在运行,发送的元素= 0 len(c)= 1 , cap(c)= 3
子go程正在运行,发送的元素= 1 len(c)= 2 , cap(c)= 3
子go程正在运行,发送的元素= 2 len(c)= 3 , cap(c)= 3
子go程结束
num = 0
num = 1
num = 2
main 结束
root@85db594393fb:/app#
注意:当channel已经满,再向里面写数据,就会阻塞;当channel为空,从里面取数据也会阻塞
关闭channel
go
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
//close可以关闭一个channel
close(c)
}()
for {
//ok如果为true表示channel没有关闭,如果为false表示channel已经关闭
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("Main Finished..")
}
channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
关闭channel后,可以继续从channel接收数据;
对于nil channel,无论收发都会被阻塞
一些配合
go
//可以使用range来迭代不断操作channel
for data := range c {
fmt.Println(data)
}
----------------------------------------------------------
//单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel的状态
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果能向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
Go Module
Go Module 是 Go 语言官方标准、内置于工具的依赖管理与项目构建系统。 它彻底取代了旧的 GOPATH模式,允许你将项目放在计算机的任何位置,并能精确、可重现地管理项目依赖的库及其版本。
核心概念
模块:一个模块就是一个项目的集合,它包含了一组相关的 Go 包,并带有一个定义该模块自身信息的 go.mod文件。你的项目本身就是一个模块。
go.mod文件:这是模块的"身份证"和"清单",位于项目的根目录。它定义了:
模块自身的路径(如 module github.com/yourname/project)。
项目所依赖的其他模块及其确切的版本号(如 require github.com/gin-gonic/gin v1.9.1)。
Go 语言的最低版本要求。
go.sum文件:这是模块的"安全锁",由工具自动生成和维护。它记录了每个依赖库的加密哈希值,确保你每次下载的依赖代码都与记录完全一致,防止被篡改,保证构建的可重现性。
核心优势(相比 GOPATH)
项目位置自由:代码可以放在任何地方,如 ~/Desktop/my-go-project。
精确的版本控制:通过语义化版本(如 v1.2.3)明确指定依赖版本,构建稳定可靠。
依赖隔离:每个项目(模块)独立管理自己的依赖,不同项目可以使用同一个库的不同版本,互不干扰。
可重现的构建:任何人拿到你的项目代码,执行 go mod tidy和 go build,都能得到完全相同的依赖树和一致的构建结果。


