【电商项目】商品搜索开发复盘(1):根据需求拆解搜索接口的设计逻辑
2026/8/26 18:21:12
Channel是Go并发编程的灵魂——“不要通过共享内存来通信,而要通过通信来共享内存”。但Channel的底层实现远比表面复杂:环形缓冲区、等待队列、goroutine的阻塞/唤醒、select的随机性……本文将深入hchan结构体,彻底揭开Channel的底层面纱。
// runtime/chan.gotypehchanstruct{qcountuint// 当前队列中的元素数量dataqsizuint// 环形队列容量buf unsafe.Pointer// 指向环形队列的指针elemsizeuint16// 每个元素的大小closeduint32// 是否已关闭elemtype*_type// 元素类型sendxuint// 发送索引recvxuint// 接收索引recvq waitq// 等待接收的goroutine队列sendq waitq// 等待发送的goroutine队列lock mutex// 互斥锁}typewaitqstruct{first*sudog last*sudog}typesudogstruct{g*g// 等待的goroutineelem unsafe.Pointer// 要发送/接收的数据指针next*sudog// 链表指针prev*sudog isSelectbool// 是否来自selectsuccessbool// 操作是否成功// ...}// 伪代码funcchansend(c*hchan,ep unsafe.Pointer,blockbool)bool{lock(&c.lock)// 情况1:有等待接收的goroutineifsg:=c.recvq.dequeue();sg!=nil{send(c,sg,ep,func(){unlock(&c.lock)})returntrue}// 情况2:缓冲区有空间ifc.qcount<c.dataqsiz{typedmemmove(c.elemtype,add(c.buf,c.sendx*c.elemsize),ep)c.sendx++c.qcount++unlock(&c.lock)returntrue}// 情况3:缓冲区满,阻塞if!block{unlock(&c.lock)returnfalse}// 将当前goroutine加入sendq等待队列gp:=getg()mysg:=acquireSudog()mysg.elem=ep c.sendq.enqueue(mysg)gopark(chanparkcommit,unsafe.Pointer(&c.lock),waitReasonChanSend,...)// goroutine被唤醒后继续执行...}// 同理,三种情况:// 1. 有等待发送的goroutine → 直接接收// 2. 缓冲区有数据 → 从环形队列读取// 3. 缓冲区空 → 阻塞(或非阻塞返回)funcclosechan(c*hchan){lock(&c.lock)// panic if already closedifc.closed!=0{unlock(&c.lock)panic("close of closed channel")}c.closed=1// 唤醒所有等待接收的goroutine(返回零值)// 唤醒所有等待发送的goroutine(panic)unlock(&c.lock)}// select的随机性——核心就是洗牌funcselectgo(cas0*scase,order0*uint16,ncasesint)(int,bool){// 1. 随机打乱case顺序(这就是select随机选择的原因)pollorder:=order0[:ncases]fori:=1;i<ncases;i++{j:=fastrandn(uint32(i+1))pollorder[i],pollorder[j]=pollorder[j],pollorder[i]}// 2. 按锁地址排序(避免死锁)lockorder:=order0[ncases:]// 排序逻辑...// 3. 遍历pollorder检查可以执行的casefor_,i:=rangepollorder{cas:=&cas0[i]// 检查是否可以非阻塞执行}// 4. 所有case都不能执行→阻塞,等待任一case可执行// 将所有goroutine加入对应channel的等待队列}关键点:
done:=make(chanstruct{})gofunc(){doWork()close(done)// 关闭通知}()<-done// 等待完成sem:=make(chanstruct{},10)// 最多10个并发for_,task:=rangetasks{sem<-struct{}{}// 获取信号量gofunc(t Task){deferfunc(){<-sem}()t.Execute()}(task)}select{caseresult:=<-resultCh:fmt.Println("结果:",result)case<-time.After(3*time.Second):fmt.Println("超时")case<-ctx.Done():fmt.Println("取消")}stopCh:=make(chanstruct{})// 多个goroutine监听同一个channelfori:=0;i<5;i++{gofunc(idint){<-stopCh fmt.Println("worker",id,"stopped")}(i)}close(stopCh)// 所有goroutine同时收到信号// 坑1:向已关闭的channel发送→panicch:=make(chanint)close(ch)ch<-1// panic!// 坑2:关闭nil channel→panicvarchchanintclose(ch)// panic!// 坑3:从已关闭的空channel接收→返回零值ch:=make(chanint)close(ch)v,ok:=<-ch// v=0, ok=false// 坑4:nil channel的select行为varchchanintselect{case<-ch:// 永远不会执行(nil channel永远阻塞)default:fmt.Println("default")}