1714 字
9 分钟
go学习
2026-05-18

感觉我学习语言的方式不对

我只是简单的在用语言都有的功能进行开发, 但是没有去了解语言的很多特性

虽然可能会有点功利,但是是不是对面试不太好🥲🥲

goroutine 协程的底层原理

原来的线程:由内核创建,由操作系统分配cpu时间片的最小单位

一个线程要有用户栈,内核栈,TCB(线程控制块,保存线程状态:阻塞、运行、就绪,优先级,CPU上下文的数据结构),通常1mb

内核栈保存寄存器状态

sync.Mutex 互斥锁,就是锁 sync.RWMutex 读写锁,读可并发,写不行

import (
mu sync.Mutex
mu2 sync.RWMutex
wp sync.WaitGroup
)
func main(){
wp.Add(1)
go func(){
defer wp.Done()
log.Println("1")
}
mu.Lock() // 互斥锁
mu2.Lock() // 写锁
mu2.RLock() // 读锁
mu.UnLock() // 互斥锁
mu2.UnLock() // 写锁
mu2.RUnLock() // 读锁
wp.Wait()
}

channel:用通信的方式共享内存,而不是用共享的内存来通信

go context底层原理

context的底层设计,我们可以概括为1个接口,4种实现与6个方法。

  • 1 个接口

    • Context 规定了context的四个基本方法
  • 4 种实现

    • emptyCtx 实现了一个空的context,可以用作根节点
    • cancelCtx 实现一个带cancel功能的context,可以主动取消
    • timerCtx 实现一个通过定时器timer和截止时间deadline定时取消的context
    • valueCtx 实现一个可以通过 key、val 两个字段来存数据的context
  • 6 个方法

    • Background 返回一个emptyCtx作为根节点
    • TODO 返回一个emptyCtx作为未知节点
    • WithCancel 返回一个cancelCtx
    • WithDeadline 返回一个timerCtx
    • WithTimeout 返回一个timerCtx
    • WithValue 返回一个valueCtx

chennel源码:

type hchan struct {
qcount uint // 队列中现存的元素总数
dataqsiz uint // 环形队列的容量(即缓冲区大小)
buf unsafe.Pointer // 指向底层连续内存数组的指针(存放数据的地方)
elemsize uint16 // 元素的大小(字节数,比如 int64 就是 8 字节)
closed uint32 // 标识通道是否关闭(0: 打开, 1: 关闭)
elemtype *_type // 元素类型(用于反射和类型安全检查)
sendx uint // 缓冲区发送索引(数组下标,下次写数据的位置)
recvx uint // 缓冲区接收索引(数组下标,下次读数据的位置)
recvq waitq // 阻塞在这里等待【接收】数据的协程链表(sudog 链表)
sendq waitq // 阻塞在这里等待【发送】数据的协程链表(sudog 链表)
lock mutex // 互斥锁!保护上面所有字段的并发安全
}
func chansend(c *hchan, ep unsafe.Pointer, block bool) bool {
// 1. 如果 channel 是空的(nil)
if c == nil {
if !block {
return false
}
// 如果是阻塞发送,直接让当前协程永久躺倒(挂起)
gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoBlockSend, 2)
throw("unreachable")
}
// 2. 核心操作开始:第一步永远是【加锁】
lock(&c.lock)
// 3. 如果 channel 已经关闭,加锁状态下抛出 panic
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
// 4. 【情况一:有现成的接收者在等数据】(无锁零拷贝优化)
// 从 recvq 队列里弹出一个正在死等数据的协程(sudog)
if sg := c.recvq.dequeue(); sg != nil {
// 找到了等待的接收者!直接把数据拷贝到它的栈内存里,并唤醒它
// 这个过程不需要经过 c.buf 缓冲区中转
send(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true
}
// 5. 【情况二:缓冲区还没满】
if c.qcount < c.dataqsiz {
// 还有空位,把数据放进环形数组 c.buf
qp := chanbuf(c, c.sendx) // 找到当前发送索引对应的内存地址
typedmemmove(c.elemtype, qp, ep) // 将数据 ep 拷贝到 qp 指向的缓冲区
c.sendx++ // 发送下标往后移
if c.sendx == c.dataqsiz { // 环形队列走到末尾,折返回头部
c.sendx = 0
}
c.qcount++ // 队列元素计数 +1
unlock(&c.lock) // 【解锁】,发送成功返回
return true
}
// 如果是异步非阻塞发送(例如 select 里的 send),缓冲区满了就直接解锁返回 false
if !block {
unlock(&c.lock)
return false
}
// 6. 【情况三:缓冲区满了,且无人在等数据】(协程挂起)
gp := getg() // 获取当前的协程对象(Goroutine)
mysg := acquireSudog() // 从对象池里打包一个临时的登记节点(sudog)
mysg.elem = ep // 把你要发送的数据地址挂在节点上
mysg.g = gp // 节点绑定当前协程
mysg.c = c // 节点绑定当前 channel
c.sendq.enqueue(mysg) // 把这个节点推进 channel 的发送等待链表
// 【关键动作】:调用 gopark 释放锁,并将当前协程转入等待状态
// 从这一刻起,当前协程不占用 CPU,直到别人读取数据将其唤醒
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
// ———————— 被唤醒后的分割线 ————————
// 当另一个协程读取了数据,它会调用 goready() 重新激活这个协程,代码从这里继续往下走
releaseSudog(mysg) // 释放临时登记节点
return true
}

channel源码总结 chennel send: 1.消息进入之后,先判断channel是否存在 (hcan struct) 如果没有的话,判断是否是异步非阻塞 如果不是,直接返回,没有channel也就不需要进行逻辑了 是,则将该协程永久挂起(这里如果实在main里面使用无缓冲channel,会将main协程 (是的,main也是协程)直接挂起,由于现在main协程都没了,runtime轮询查找可用的协程,但是找不到,只剩堆里面一个channel在运作,直接判断死锁) 之后先加锁 如果channel关闭了,直接panic 如果有订阅者,就把消息发给这个人(FIFO 先到先得) 没有订阅者,缓冲区还没满,写入缓冲区 缓冲区满了,挂起当前协程

// 运行时的底层判定逻辑(伪代码)
func checkdead() { run := sched.runnablecnt // 当前可运行的协程数量
if run > 0 { return // 还有人能跑,不算死锁
} // 关键账目对比: // nmidlelocked 是被锁住的线程,sysmon 是系统监控 // 除去这些后,看看全天下总共还有多少个活着的协程(包括睡着的)
aliveCount := runtime.gcount() if aliveCount == 0 { // 如果连睡着的协程都没有,说明程序是正常运行完自然退出的
return } // 【致命判定】:如果活着的协程数量 > 0(比如 main 还在),但可运行的数量为 0 // 这意味着:这群活着的协程全部在睡觉,且断绝了所有被唤醒的可能!
panic("fatal error: all goroutines are asleep - deadlock!")
}

同步阻塞:

ch := make(chan int)

同步非阻塞:

select{
case data <- ch:
printlf("发送成功")
default:
println("干一些别的事情")
}

异步非阻塞: 网络IO调用,我在等待数据的时候,把使用数据的回调函数注册,数据到了自动调用留下的回调函数

随学随写了(

go学习
https://lennyface24.github.io/posts/daily-2026-05-18/
作者
Itzlennyface
发布于
2026-05-18
许可协议
CC BY-NC-SA 4.0