golang channel源码
解析
数据结构
hchan:channel 数据结构
qcount:当前 channel 中存在多少个元素;
dataqsize: 当前 channel 能存放的元素容量;
buf:channel 中用于存放元素的环形缓冲区;
elemsize:channel 元素类型的大小;
closed:标识 channel 是否关闭;
elemtype:channel 元素类型;
sendx:发送元素进入环形缓冲区的 index;
recvx:接收元素所处的环形缓冲区的 index;
recvq:因接收而陷入阻塞的协程队列;
sendq:因发送而陷入阻塞的协程队列;
type hchan struct {qcount uint // total data in the queuedataqsiz uint // size of the circular queuebuf unsafe.Pointer // points to an array of dataqsiz elementselemsize uint16closed uint32timer *timer // timer feeding this chanelemtype *_type // element typesendx uint // send indexrecvx uint // receive indexrecvq waitq // list of recv waiterssendq waitq // list of send waiters// lock protects all fields in hchan, as well as several// fields in sudogs blocked on this channel.//// Do not change another G's status while holding this lock// (in particular, do not ready a G), as this can deadlock// with stack shrinking.lock mutex
}
waitq:阻塞的协程队列
first:队列头部
last:队列尾部
type waitq struct {first *sudoglast *sudog
}
//转到:链接名reflect_makechan reflect.makechan
func reflect_makechan(t *chantype, size int) *hchan {return makechan(t, size)
}func makechan64(t *chantype, size int64) *hchan {if int64(int(size)) != size {panic(plainError("makechan: size out of range"))}return makechan(t, int(size))
}func makechan(t *chantype, size int) *hchan {elem := t.Elem//编译器会检查这一点,但要安全。if elem.Size_ >= 1<<16 {throw("makechan: invalid channel element type")}if hchanSize%maxAlign != 0 || elem.Align_ > maxAlign {throw("makechan: bad alignment")}mem, overflow := math.MulUintptr(elem.Size_, uintptr(size))if overflow || mem > maxAlloc-hchanSize || size < 0 {panic(plainError("makechan: size out of range"))}//当buf中存储的元素不包含指针时,Hchan不包含GC感兴趣的指针。//buf指向相同的分配,elemtype是持久的。//SudoG是从其所属线程引用的,因此无法收集它们。//TODO(dvyukov,rlh):重新思考收集器何时可以移动分配的对象。var c *hchanswitch {case mem == 0://队列或元素大小为零。c = (*hchan)(mallocgc(hchanSize, nil, true))
//竞赛检测器使用此位置进行同步。c.buf = c.raceaddr()case !elem.Pointers()://元素不包含指针。//在一次通话中分配hchan和buf。c = (*hchan)(mallocgc(hchanSize+mem, nil, true))c.buf = add(unsafe.Pointer(c), hchanSize)default://元素包含指针。c = new(hchan)c.buf = mallocgc(mem, elem, true)}c.elemsize = uint16(elem.Size_)c.elemtype = elemc.dataqsiz = uint(size)lockInit(&c.lock, lockRankHchan)if debugChan {print("makechan: chan=", c, "; elemsize=", elem.Size_, "; dataqsiz=", size, "\n")}return c
}//chanbuf(c,i)是指向缓冲区中第i个槽的指针。
//
//chanbuf应该是一个内部细节,
//但广泛使用的包使用linkname访问它。
//耻辱大厅的著名成员包括:
//-github/fjl/memsize
//
//不要删除或更改类型签名。
//见go.dev/issue/67401。
//
//go:链接名chanbuf
func chanbuf(c *hchan, i uint) unsafe.Pointer {return add(c.buf, uintptr(i)*uintptr(c.elemsize))
}//full报告c上的发送是否会阻塞(即通道已满)。
//它使用可变状态的单个字大小的读取,因此尽管
//答案瞬间为真,正确答案可能已经改变
//当调用函数接收到返回值时。
func full(c *hchan) bool {//c.dataqsiz是不可变的(在创建通道后从不写入)
//因此,在信道操作期间的任何时候都可以安全地读取。if c.dataqsiz == 0 {
//假设指针读取是原子性的。return c.recvq.first == nil}
//假设uint读取是轻松原子的。return c.qcount == c.dataqsiz
}//编译代码中c<-x的入口点。
//
//去:nosplit
func chansend1(c *hchan, elem unsafe.Pointer) {chansend(c, elem, true, getcallerpc())
}/*
*通用单通道发送/接收
*如果block不是nil,
*那么协议将不会
*睡觉,但如果可以的话就回来
*不完整。
*
*睡眠可以用g.param==nil唤醒
*当睡眠中涉及的通道
*已关闭。最容易循环并重新运行
*手术;我们会看到它现在已经关闭了。
*/
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {if c == nil {if !block {return false}gopark(nil, nil, waitReasonChanSendNilChan, traceBlockForever, 2)throw("unreachable")}if debugChan {print("chansend: chan=", c, "\n")}if raceenabled {racereadpc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(chansend))}//快速路径:检查未获取锁的非阻塞操作是否失败。
//
//在观察到通道未关闭后,我们观察到通道
//未准备好发送。这些观察中的每一个都是一个单词大小的阅读
//(第一个c.closed和第二个full())。
//因为封闭通道无法从“准备发送”转换为
//“未准备好发送”,即使通道在两次观测之间关闭,
//它们暗示了两者之间的某个时刻,当时通道都还没有关闭
//并且尚未准备好发送。我们表现得好像在那一刻观察到了通道,
//并报告发送无法继续。
//
//如果在这里对读取进行重新排序是可以的:如果我们观察到通道不是
//准备发送,然后观察到它没有关闭,这意味着
//在第一次观察期间,通道没有关闭。然而,这里什么都没有
//保证向前发展。我们依靠解锁的副作用
//chanrecv()和closechan()来更新这个线程的c.closed和full()视图。if !block && c.closed == 0 && full(c) {return false}var t0 int64if blockprofilerate > 0 {t0 = cputicks()}lock(&c.lock)if c.closed != 0 {unlock(&c.lock)panic(plainError("send on closed channel"))}if sg := c.recvq.dequeue(); sg != nil {
//找到一个正在等待的接收器。我们传递要发送的值
//绕过信道缓冲器(如果有的话)直接传输到接收器。send(c, sg, ep, func() { unlock(&c.lock) }, 3)return true}if c.qcount < c.dataqsiz {
//通道缓冲区中有可用空间。取消要发送的元素的队列。qp := chanbuf(c, c.sendx)if raceenabled {racenotify(c, c.sendx, nil)}typedmemmove(c.elemtype, qp, ep)c.sendx++if c.sendx == c.dataqsiz {c.sendx = 0}c.qcount++unlock(&c.lock)return true}if !block {unlock(&c.lock)return false}//封锁频道。某个接收器将为我们完成操作。gp := getg()mysg := acquireSudog()mysg.releasetime = 0if t0 != 0 {mysg.releasetime = -1}
//在分配elem和查询mysg之间没有堆栈拆分
//在gp.waiting上,copystack可以在哪里找到它。mysg.elem = epmysg.waitlink = nilmysg.g = gpmysg.isSelect = falsemysg.c = cgp.waiting = mysggp.param = nilc.sendq.enqueue(mysg)
//向任何试图缩小我们堆栈的人发出信号,表明我们正在努力
//把车停在航道上。此G状态之间的窗口
//当我们设置gp.activeTackChans时
//堆叠收缩。gp.parkingOnChan.Store(true)gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceBlockChanSend, 2)//确保发送的值保持有效,直到
//接收者将其复制出来。sudog有一个指针指向
//堆栈对象,但sudogs不被视为
//堆栈跟踪器。KeepAlive(ep)//有人把我们吵醒了。if mysg != gp.waiting {throw("G waiting list is corrupted")}gp.waiting = nilgp.activeStackChans = falseclosed := !mysg.successgp.param = nilif mysg.releasetime > 0 {blockevent(mysg.releasetime-t0, 2)}mysg.c = nilreleaseSudog(mysg)if closed {if c.closed == 0 {throw("chansend: spurious wakeup")}panic(plainError("send on closed channel"))}return true
}//send在空信道c上处理发送操作。
//发送方发送的值ep被复制到接收方sg。
//然后,接收器被唤醒,继续它的快乐之路。
//通道c必须为空并锁定。send使用unlockf解锁c。
//sg必须已经从c中退出。
//ep必须为非nil,并指向堆或调用者的堆栈。
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {if raceenabled {if c.dataqsiz == 0 {racesync(c, sg)} else {//假装我们穿过缓冲区,即使
//我们直接复制。请注意,我们需要递增
//仅在启用赛道时才显示头部/尾部位置。racenotify(c, c.recvx, nil)racenotify(c, c.recvx, sg)c.recvx++if c.recvx == c.dataqsiz {c.recvx = 0}c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz}}if sg.elem != nil {sendDirect(c.elemtype, sg, ep)sg.elem = nil}gp := sg.gunlockf()gp.param = unsafe.Pointer(sg)sg.success = trueif sg.releasetime != 0 {sg.releasetime = cputicks()}goready(gp, skip+1)
}// timerchandrain removes all elements in channel c's buffer.
// It reports whether any elements were removed.
// Because it is only intended for timers, it does not
// handle waiting senders at all (all timer channels
// use non-blocking sends to fill the buffer).
func timerchandrain(c *hchan) bool {// Note: Cannot use empty(c) because we are called// while holding c.timer.sendLock, and empty(c) will// call c.timer.maybeRunChan, which will deadlock.// We are emptying the channel, so we only care about// the count, not about potentially filling it up.if atomic.Loaduint(&c.qcount) == 0 {return false}lock(&c.lock)any := falsefor c.qcount > 0 {any = truetypedmemclr(c.elemtype, chanbuf(c, c.recvx))c.recvx++if c.recvx == c.dataqsiz {c.recvx = 0}c.qcount--}unlock(&c.lock)return any
}// Sends and receives on unbuffered or empty-buffered channels are the
// only operations where one running goroutine writes to the stack of
// another running goroutine. The GC assumes that stack writes only
// happen when the goroutine is running and are only done by that
// goroutine. Using a write barrier is sufficient to make up for
// violating that assumption, but the write barrier has to work.
// typedmemmove will call bulkBarrierPreWrite, but the target bytes
// are not in the heap, so that will not help. We arrange to call
// memmove and typeBitsBulkBarrier instead.func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {// src is on our stack, dst is a slot on another stack.// Once we read sg.elem out of sg, it will no longer// be updated if the destination's stack gets copied (shrunk).// So make sure that no preemption points can happen between read & use.dst := sg.elemtypeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.Size_)// No need for cgo write barrier checks because dst is always// Go memory.memmove(dst, src, t.Size_)
}func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {// dst is on our stack or the heap, src is on another stack.// The channel is locked, so src will not move during this// operation.src := sg.elemtypeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.Size_)memmove(dst, src, t.Size_)
}func closechan(c *hchan) {if c == nil {panic(plainError("close of nil channel"))}lock(&c.lock)if c.closed != 0 {unlock(&c.lock)panic(plainError("close of closed channel"))}if raceenabled {callerpc := getcallerpc()racewritepc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(closechan))racerelease(c.raceaddr())}c.closed = 1var glist gList// release all readersfor {sg := c.recvq.dequeue()if sg == nil {break}if sg.elem != nil {typedmemclr(c.elemtype, sg.elem)sg.elem = nil}if sg.releasetime != 0 {sg.releasetime = cputicks()}gp := sg.ggp.param = unsafe.Pointer(sg)sg.success = falseif raceenabled {raceacquireg(gp, c.raceaddr())}glist.push(gp)}// release all writers (they will panic)for {sg := c.sendq.dequeue()if sg == nil {break}sg.elem = nilif sg.releasetime != 0 {sg.releasetime = cputicks()}gp := sg.ggp.param = unsafe.Pointer(sg)sg.success = falseif raceenabled {raceacquireg(gp, c.raceaddr())}glist.push(gp)}unlock(&c.lock)// Ready all Gs now that we've dropped the channel lock.for !glist.empty() {gp := glist.pop()gp.schedlink = 0goready(gp, 3)}
}// empty reports whether a read from c would block (that is, the channel is
// empty). It is atomically correct and sequentially consistent at the moment
// it returns, but since the channel is unlocked, the channel may become
// non-empty immediately afterward.
func empty(c *hchan) bool {// c.dataqsiz is immutable.if c.dataqsiz == 0 {return atomic.Loadp(unsafe.Pointer(&c.sendq.first)) == nil}// c.timer is also immutable (it is set after make(chan) but before any channel operations).// All timer channels have dataqsiz > 0.if c.timer != nil {c.timer.maybeRunChan()}return atomic.Loaduint(&c.qcount) == 0
}// entry points for <- c from compiled code.
//
//go:nosplit
func chanrecv1(c *hchan, elem unsafe.Pointer) {chanrecv(c, elem, true)
}//go:nosplit
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {_, received = chanrecv(c, elem, true)return
}// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
// A non-nil ep must point to the heap or the caller's stack.
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {// raceenabled: don't need to check ep, as it is always on the stack// or is new memory allocated by reflect.if debugChan {print("chanrecv: chan=", c, "\n")}if c == nil {if !block {return}gopark(nil, nil, waitReasonChanReceiveNilChan, traceBlockForever, 2)throw("unreachable")}if c.timer != nil {c.timer.maybeRunChan()}// Fast path: check for failed non-blocking operation without acquiring the lock.if !block && empty(c) {// After observing that the channel is not ready for receiving, we observe whether the// channel is closed.//// Reordering of these checks could lead to incorrect behavior when racing with a close.// For example, if the channel was open and not empty, was closed, and then drained,// reordered reads could incorrectly indicate "open and empty". To prevent reordering,// we use atomic loads for both checks, and rely on emptying and closing to happen in// separate critical sections under the same lock. This assumption fails when closing// an unbuffered channel with a blocked send, but that is an error condition anyway.if atomic.Load(&c.closed) == 0 {// Because a channel cannot be reopened, the later observation of the channel// being not closed implies that it was also not closed at the moment of the// first observation. We behave as if we observed the channel at that moment// and report that the receive cannot proceed.return}// The channel is irreversibly closed. Re-check whether the channel has any pending data// to receive, which could have arrived between the empty and closed checks above.// Sequential consistency is also required here, when racing with such a send.if empty(c) {// The channel is irreversibly closed and empty.if raceenabled {raceacquire(c.raceaddr())}if ep != nil {typedmemclr(c.elemtype, ep)}return true, false}}var t0 int64if blockprofilerate > 0 {t0 = cputicks()}lock(&c.lock)if c.closed != 0 {if c.qcount == 0 {if raceenabled {raceacquire(c.raceaddr())}unlock(&c.lock)if ep != nil {typedmemclr(c.elemtype, ep)}return true, false}// The channel has been closed, but the channel's buffer have data.} else {// Just found waiting sender with not closed.if sg := c.sendq.dequeue(); sg != nil {// Found a waiting sender. If buffer is size 0, receive value// directly from sender. Otherwise, receive from head of queue// and add sender's value to the tail of the queue (both map to// the same buffer slot because the queue is full).recv(c, sg, ep, func() { unlock(&c.lock) }, 3)return true, true}}if c.qcount > 0 {// Receive directly from queueqp := chanbuf(c, c.recvx)if raceenabled {racenotify(c, c.recvx, nil)}if ep != nil {typedmemmove(c.elemtype, ep, qp)}typedmemclr(c.elemtype, qp)c.recvx++if c.recvx == c.dataqsiz {c.recvx = 0}c.qcount--unlock(&c.lock)return true, true}if !block {unlock(&c.lock)return false, false}// no sender available: block on this channel.gp := getg()mysg := acquireSudog()mysg.releasetime = 0if t0 != 0 {mysg.releasetime = -1}// No stack splits between assigning elem and enqueuing mysg// on gp.waiting where copystack can find it.mysg.elem = epmysg.waitlink = nilgp.waiting = mysgmysg.g = gpmysg.isSelect = falsemysg.c = cgp.param = nilc.recvq.enqueue(mysg)if c.timer != nil {blockTimerChan(c)}// Signal to anyone trying to shrink our stack that we're about// to park on a channel. The window between when this G's status// changes and when we set gp.activeStackChans is not safe for// stack shrinking.gp.parkingOnChan.Store(true)gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceBlockChanRecv, 2)// someone woke us upif mysg != gp.waiting {throw("G waiting list is corrupted")}if c.timer != nil {unblockTimerChan(c)}gp.waiting = nilgp.activeStackChans = falseif mysg.releasetime > 0 {blockevent(mysg.releasetime-t0, 2)}success := mysg.successgp.param = nilmysg.c = nilreleaseSudog(mysg)return true, success
}// recv processes a receive operation on a full channel c.
// There are 2 parts:
// 1. The value sent by the sender sg is put into the channel
// and the sender is woken up to go on its merry way.
// 2. The value received by the receiver (the current G) is
// written to ep.
//
// For synchronous channels, both values are the same.
// For asynchronous channels, the receiver gets its data from
// the channel buffer and the sender's data is put in the
// channel buffer.
// Channel c must be full and locked. recv unlocks c with unlockf.
// sg must already be dequeued from c.
// A non-nil ep must point to the heap or the caller's stack.
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {if c.dataqsiz == 0 {if raceenabled {racesync(c, sg)}if ep != nil {// copy data from senderrecvDirect(c.elemtype, sg, ep)}} else {// Queue is full. Take the item at the// head of the queue. Make the sender enqueue// its item at the tail of the queue. Since the// queue is full, those are both the same slot.qp := chanbuf(c, c.recvx)if raceenabled {racenotify(c, c.recvx, nil)racenotify(c, c.recvx, sg)}// copy data from queue to receiverif ep != nil {typedmemmove(c.elemtype, ep, qp)}// copy data from sender to queuetypedmemmove(c.elemtype, qp, sg.elem)c.recvx++if c.recvx == c.dataqsiz {c.recvx = 0}c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz}sg.elem = nilgp := sg.gunlockf()gp.param = unsafe.Pointer(sg)sg.success = trueif sg.releasetime != 0 {sg.releasetime = cputicks()}goready(gp, skip+1)
}func chanparkcommit(gp *g, chanLock unsafe.Pointer) bool {// There are unlocked sudogs that point into gp's stack. Stack// copying must lock the channels of those sudogs.// Set activeStackChans here instead of before we try parking// because we could self-deadlock in stack growth on the// channel lock.gp.activeStackChans = true// Mark that it's safe for stack shrinking to occur now,// because any thread acquiring this G's stack for shrinking// is guaranteed to observe activeStackChans after this store.gp.parkingOnChan.Store(false)// Make sure we unlock after setting activeStackChans and// unsetting parkingOnChan. The moment we unlock chanLock// we risk gp getting readied by a channel operation and// so gp could continue running before everything before// the unlock is visible (even to gp itself).unlock((*mutex)(chanLock))return true
}// compiler implements
//
// select {
// case c <- v:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if selectnbsend(c, v) {
// ... foo
// } else {
// ... bar
// }
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {return chansend(c, elem, false, getcallerpc())
}// compiler implements
//
// select {
// case v, ok = <-c:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if selected, ok = selectnbrecv(&v, c); selected {
// ... foo
// } else {
// ... bar
// }
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected, received bool) {return chanrecv(c, elem, false)
}//go:linkname reflect_chansend reflect.chansend0
func reflect_chansend(c *hchan, elem unsafe.Pointer, nb bool) (selected bool) {return chansend(c, elem, !nb, getcallerpc())
}//go:linkname reflect_chanrecv reflect.chanrecv
func reflect_chanrecv(c *hchan, nb bool, elem unsafe.Pointer) (selected bool, received bool) {return chanrecv(c, elem, !nb)
}func chanlen(c *hchan) int {if c == nil {return 0}async := debug.asynctimerchan.Load() != 0if c.timer != nil && async {c.timer.maybeRunChan()}if c.timer != nil && !async {// timer channels have a buffered implementation// but present to users as unbuffered, so that we can// undo sends without users noticing.return 0}return int(c.qcount)
}func chancap(c *hchan) int {if c == nil {return 0}if c.timer != nil {async := debug.asynctimerchan.Load() != 0if async {return int(c.dataqsiz)}// timer channels have a buffered implementation// but present to users as unbuffered, so that we can// undo sends without users noticing.return 0}return int(c.dataqsiz)
}//go:linkname reflect_chanlen reflect.chanlen
func reflect_chanlen(c *hchan) int {return chanlen(c)
}//go:linkname reflectlite_chanlen internal/reflectlite.chanlen
func reflectlite_chanlen(c *hchan) int {return chanlen(c)
}//go:linkname reflect_chancap reflect.chancap
func reflect_chancap(c *hchan) int {return chancap(c)
}//go:linkname reflect_chanclose reflect.chanclose
func reflect_chanclose(c *hchan) {closechan(c)
}func (q *waitq) enqueue(sgp *sudog) {sgp.next = nilx := q.lastif x == nil {sgp.prev = nilq.first = sgpq.last = sgpreturn}sgp.prev = xx.next = sgpq.last = sgp
}func (q *waitq) dequeue() *sudog {for {sgp := q.firstif sgp == nil {return nil}y := sgp.nextif y == nil {q.first = nilq.last = nil} else {y.prev = nilq.first = ysgp.next = nil // mark as removed (see dequeueSudoG)}// if a goroutine was put on this queue because of a// select, there is a small window between the goroutine// being woken up by a different case and it grabbing the// channel locks. Once it has the lock// it removes itself from the queue, so we won't see it after that.// We use a flag in the G struct to tell us when someone// else has won the race to signal this goroutine but the goroutine// hasn't removed itself from the queue yet.if sgp.isSelect && !sgp.g.selectDone.CompareAndSwap(0, 1) {continue}return sgp}
}func (c *hchan) raceaddr() unsafe.Pointer {// Treat read-like and write-like operations on the channel to// happen at this address. Avoid using the address of qcount// or dataqsiz, because the len() and cap() builtins read// those addresses, and we don't want them racing with// operations like close().return unsafe.Pointer(&c.buf)
}func racesync(c *hchan, sg *sudog) {racerelease(chanbuf(c, 0))raceacquireg(sg.g, chanbuf(c, 0))racereleaseg(sg.g, chanbuf(c, 0))raceacquire(chanbuf(c, 0))
}// Notify the race detector of a send or receive involving buffer entry idx
// and a channel c or its communicating partner sg.
// This function handles the special case of c.elemsize==0.
func racenotify(c *hchan, idx uint, sg *sudog) {// We could have passed the unsafe.Pointer corresponding to entry idx// instead of idx itself. However, in a future version of this function,// we can use idx to better handle the case of elemsize==0.// A future improvement to the detector is to call TSan with c and idx:// this way, Go will continue to not allocating buffer entries for channels// of elemsize==0, yet the race detector can be made to handle multiple// sync objects underneath the hood (one sync object per idx)qp := chanbuf(c, idx)// When elemsize==0, we don't allocate a full buffer for the channel.// Instead of individual buffer entries, the race detector uses the// c.buf as the only buffer entry. This simplification prevents us from// following the memory model's happens-before rules (rules that are// implemented in racereleaseacquire). Instead, we accumulate happens-before// information in the synchronization object associated with c.buf.if c.elemsize == 0 {if sg == nil {raceacquire(qp)racerelease(qp)} else {raceacquireg(sg.g, qp)racereleaseg(sg.g, qp)}} else {if sg == nil {racereleaseacquire(qp)} else {racereleaseacquireg(sg.g, qp)}}
}
其他
const (maxAlign = 8hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))debugChan = false
)相关文章:
golang channel源码
解析 数据结构 hchan:channel 数据结构 qcount:当前 channel 中存在多少个元素; dataqsize: 当前 channel 能存放的元素容量; buf:channel 中用于存放元素的环形缓冲区; elemsize:channel 元素…...
小刚说C语言刷题——1033 判断奇偶数
1.题目描述 输入一个整数,判断是否为偶数。是输出 y e s ,否则输出n o。 输入 输入只有一行,包括 1个整数(该整数在 1∼10000的范围内)。 输出 输出只有一行。(注意输出格式,具体请看下方提…...
2025TGCTF Web WP复现
AAA 偷渡阴平 <?php$tgctf2025$_GET[tgctf2025];if(!preg_match("/0|1|[3-9]|\~|\|\|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\|\|\{|\[|\]|\}|\:|\|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $tgctf2025)){//hint:你可以对着键盘…...
基于DeepSeek的考研暑假日志分析
注:我去年考研时写了日志,大致记录了我每天的主要活动。由于过于琐碎,一直没有翻看。突发奇想,现在利用deepseek总结其中规律。 从你的日志中可以总结出以下规律和活动兴衰起落: 一、学习活动规律与演变 …...
「GitHub热榜」AIGC系统源码:AI问答+绘画+PPT+音乐生成一站式
—零门槛搭建私有化AI内容工厂,源码开放商业落地指南 为什么全栈AIGC系统成为企业刚需? 1. 传统方案的致命缺陷 痛点 使用ChatGPTMidjourneyCanva 本全栈方案 工具割裂 需切换5平台 一个系统全搞定 成本 年费50万 一次部署永久免费 数据安全 …...
AWS上构建基于自然语言的数值和符号计算系统
我想要实现一个通过使用C#、Semantic Kernel库、OpenAI GPT 4的API和以下使用C#开源库MathNet实现通过中文自然语言提示词中包含LATEX代码输入到系统,通过以下符号和数值计算和其它符号和数值计算程序输出计算结果和必要步骤的应用,这样的数学计算使用程序直接产生结果,可以…...
【C++】 —— 笔试刷题day_19
一、小易的升级之路 题目解析 小易现在要打游戏,现在游戏角色的初始能力值为a,我们会遇到n个怪,这些怪物的防御值为b1、b2、b3...,如果我们的能力值要高于或者等于怪物的防御值,那我们的能力值就会加bi;如…...
解决 Spring Boot 多数据源环境下事务管理器冲突问题(非Neo4j请求标记了 @Transactional 尝试启动Neo4j的事务管理器)
0. 写在前面 到底遇到了什么问题? 简洁版: 在 Oracle 与 Neo4j 共存的多数据源项目中,一个仅涉及 Oracle 操作的请求,却因为 Neo4j 连接失败而报错。根本原因是 Spring 的默认事务管理器错误地指向了 Neo4j,导致不相…...
Oracle日志系统之重做日志和归档日志
Oracle日志系统之重做日志和归档日志 重做日志归档日志 本文讨论Oracle日志系统中对数据恢复非常重要的两个日志:重做日志和归档日志。 重做日志 重做日志,英文名Redo Log,顾名思义,是用来数据重做的,主要使用场景是事…...
Kubernetes》》K8S》》Pod的健康检查
K8s概念总结 》》》Pod的生命周期阶段 Pod的生命周期可以简单描述:首先Pod被创建,紧接着Pod被调度到Node节点进行部署。 Pod是非常忠诚的,一旦被分配到Node节点后,就不会离开这个Node节点,直到它被删除,删除…...
计算机视觉——基于使用 OpenCV 与 Python 实现相机标定畸变校正
概述 相机标定是一种旨在通过确定相机的内参(焦距、光学中心、畸变系数)和外参(相机的位置和方向),提高图像在现实世界中的几何精度的过程。该过程可以纠正相机拍摄的图像中的畸变,使相机能够准确感知现实…...
Python作业4 文本词云统计,生成词云
编写程序,统计两会政府工作报告热词频率,并生成词云。 2025两会政府工作报告 import jieba import wordcloud from collections import Counter import re# 读取文件 with open("gov.txt", "r", encoding"gbk") as f:t …...
Jenkins 2.492.2 LTS 重置管理员密码
文章目录 1. Jenkins 关闭用户认证2. jenkins 修改密码 如果忘记了 Jenkins 的管理员密码的话,也不用担心,只要你有权限访问 Jenkins 的根目录,就可以轻松地重置密码。 1. Jenkins 关闭用户认证 // 查看 jenkins 家目录(使用 doc…...
1. python开发小笔记
本文件记录一些实用的python小知识,会一直更新 1. import路径 1.1 python的import搜索路径可以用sys.path查看: import sys print(sys.path) 1.2 python的搜索目录有: 本脚本所在目录环境变量PYTHONPATH指定的目录标准库目录,通…...
【裁判文书网DES3数据解密】逆向分析
点击翻页,出现请求,可以看到请求参数有个ciphertext密文,响应数据也是密文 打上断点,点击翻页,断住 可以看到postData里面的ciphertext已经生成 往前跟栈,可以发现是var ciphertext cipher(); funct…...
探索 JavaScript 中的 Promise 高级用法与实战
在现代 Web 开发中,异步编程已成为不可或缺的一部分。JavaScript 作为 Web 开发的核心语言,提供了多种处理异步操作的方式,其中 Promise 对象因其简洁、强大的特性而备受青睐。本文将深入探讨 Promise 的高级用法,并结合实际案例&…...
【dify实战】agent结合deepseek实现基于自然语言的数据库问答、Echarts可视化展示、Excel报表下载
使用dify agent实现数据库智能问答,echarts可视化展示,excel报表下载 观看视频,您将学会 在dify下如何快速的构建一个agent,来完成数据分析工作;如何在AI的回复中展示可视化的图表;如何在AI 的回复中加入E…...
C++学习:六个月从基础到就业——面向对象编程:接口设计
C学习:六个月从基础到就业——面向对象编程:接口设计 本文是我C学习之旅系列的第十五篇技术文章,重点讨论在C中进行接口设计的原则、技术和最佳实践。查看完整系列目录了解更多内容。 引言 在面向对象的软件开发中,良好的接口设计…...
花园灌溉问题
#include <bits/stdc.h> using namespace std;// 设置最大行列数(题目限制 n, m ≤ 100) const int N 104;// 标记某个格子是否已经被水浇灌 bool used[N][N];// 队列,用于 BFS,存储当前水源的位置 queue<pair<int,i…...
《AI大模型应知应会100篇》第22篇:系统提示词(System Prompt)设计与优化
第22篇:系统提示词(System Prompt)设计与优化 摘要 在大语言模型(LLM)应用中,系统提示词(System Prompt)是控制模型行为的核心工具之一。它不仅定义了模型的身份、角色和行为规范,还直接影响输…...
Jsp技术入门指南【六】jsp脚本原理及隐式对象
Jsp技术入门指南【六】jsp脚本原理及隐式对象 前言一、JSP 脚本元素1.1 声明1.2 表达式1.3 脚本标签 二、JSP 的隐式对象是什么三、隐式对象详解outrequestsessionapplicationconfigexception 前言 在之前的博客中,我们已经介绍了JSP的环境搭建、编译文件查找以及生…...
transient关键字深度解析
Java transient 关键字深度解析 1. 核心概念 (1) 基本定义 作用:标记字段不参与序列化 适用场景: 敏感数据(如密码、密钥) 临时计算字段 依赖运行时环境的字段(如Thread对象) (2) 语法示例 java public class User implements Serializable {private String username…...
Jsp技术入门指南【五】详细讲解jsp结构页面
Jsp技术入门指南【五】详细讲解jsp结构页面 前言一、JSP页面的结构二、JSP页面的部件1. 指令(核心控制部件)2. 动作(页面交互部件,了解即可)3. 脚本(Java逻辑嵌入部件) 三、JSP指令详解1.1 JSP指…...
Beyond Compare 30天评估到期 解决方法
Beyond Compare 30天评估到期 解决方法 一、问题二、解决办法2.1 第一步:打开注册表2.2 第二步:删除cacheID 三、效果 一、问题 Beyond Compare提示评估到期,重装也无效,只需简单两步,轻轻松松出困境。 二、解决办法…...
探索蓝桥杯:嵌入式开发技巧分享与实践
在信息技术飞速发展的今天,嵌入式系统作为物联网和智能设备的核心技术之一,正扮演着愈发重要的角色。蓝桥杯作为国内知名的科技竞赛平台,为广大学生和科技爱好者提供了展示自己嵌入式开发能力的舞台。在这场竞赛中,参赛者不仅需要…...
Arduino无线体感机器手——问题汇总
文章不介绍具体参数,有需求可去网上搜索。 特别声明:不论年龄,不看学历。既然你对这个领域的东西感兴趣,就应该不断培养自己提出问题、思考问题、探索答案的能力。 提出问题:提出问题时,应说明是哪款产品&a…...
学习设计模式《一》——简单工厂
一、基础概念 1.1、接口 简单的说:接口是【用来实现类的行为定义、约束类的行为】(即:定义可以做什么);接口可以包含【实例方法】、【属性】、【事件】、【索引器】或这四种成员类型的任意组合。 接口的优点࿱…...
python有序列表
您的代码整体结构良好,但存在一些关键错误和优化点。以下是对代码的详细评价及改进建议:---### 主要问题1. **add方法中的链表断裂问题**- **问题描述**:当向链表中间插入节点时,未正确设置新节点的next,导致后续节点丢…...
使用Lombok @Builder 收参报错提示没有无参构造方法的原因与解决办法
使用Lombok Builder 收参报错提示没有无参构造方法的原因与解决办法 类上加了Builder之后接口接收前端传来的参数报错:(no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) 1.解决办法…...
010数论——算法备赛
数论 模运算 一般求余都是对正整数的操作,如果对负数,不同编程语言结果可能不同。 C/javapythona>m,0<a%m<m-1 a<m,a%ma~5%32~-5%3 -21(-5)%(-3) -2~5%(-3)2-1正数:(ab)%m((a%m)(b%m))%m~正数ÿ…...
