...
Run Format

Source file src/runtime/chan.go

     1	// Copyright 2014 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	package runtime
     6	
     7	// This file contains the implementation of Go channels.
     8	
     9	// Invariants:
    10	//  At least one of c.sendq and c.recvq is empty.
    11	// For buffered channels, also:
    12	//  c.qcount > 0 implies that c.recvq is empty.
    13	//  c.qcount < c.dataqsiz implies that c.sendq is empty.
    14	import (
    15		"runtime/internal/atomic"
    16		"unsafe"
    17	)
    18	
    19	const (
    20		maxAlign  = 8
    21		hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))
    22		debugChan = false
    23	)
    24	
    25	type hchan struct {
    26		qcount   uint           // total data in the queue
    27		dataqsiz uint           // size of the circular queue
    28		buf      unsafe.Pointer // points to an array of dataqsiz elements
    29		elemsize uint16
    30		closed   uint32
    31		elemtype *_type // element type
    32		sendx    uint   // send index
    33		recvx    uint   // receive index
    34		recvq    waitq  // list of recv waiters
    35		sendq    waitq  // list of send waiters
    36	
    37		// lock protects all fields in hchan, as well as several
    38		// fields in sudogs blocked on this channel.
    39		//
    40		// Do not change another G's status while holding this lock
    41		// (in particular, do not ready a G), as this can deadlock
    42		// with stack shrinking.
    43		lock mutex
    44	}
    45	
    46	type waitq struct {
    47		first *sudog
    48		last  *sudog
    49	}
    50	
    51	//go:linkname reflect_makechan reflect.makechan
    52	func reflect_makechan(t *chantype, size int64) *hchan {
    53		return makechan(t, size)
    54	}
    55	
    56	func makechan(t *chantype, size int64) *hchan {
    57		elem := t.elem
    58	
    59		// compiler checks this but be safe.
    60		if elem.size >= 1<<16 {
    61			throw("makechan: invalid channel element type")
    62		}
    63		if hchanSize%maxAlign != 0 || elem.align > maxAlign {
    64			throw("makechan: bad alignment")
    65		}
    66		if size < 0 || int64(uintptr(size)) != size || (elem.size > 0 && uintptr(size) > (_MaxMem-hchanSize)/elem.size) {
    67			panic(plainError("makechan: size out of range"))
    68		}
    69	
    70		var c *hchan
    71		if elem.kind&kindNoPointers != 0 || size == 0 {
    72			// Allocate memory in one call.
    73			// Hchan does not contain pointers interesting for GC in this case:
    74			// buf points into the same allocation, elemtype is persistent.
    75			// SudoG's are referenced from their owning thread so they can't be collected.
    76			// TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
    77			c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true))
    78			if size > 0 && elem.size != 0 {
    79				c.buf = add(unsafe.Pointer(c), hchanSize)
    80			} else {
    81				// race detector uses this location for synchronization
    82				// Also prevents us from pointing beyond the allocation (see issue 9401).
    83				c.buf = unsafe.Pointer(c)
    84			}
    85		} else {
    86			c = new(hchan)
    87			c.buf = newarray(elem, int(size))
    88		}
    89		c.elemsize = uint16(elem.size)
    90		c.elemtype = elem
    91		c.dataqsiz = uint(size)
    92	
    93		if debugChan {
    94			print("makechan: chan=", c, "; elemsize=", elem.size, "; elemalg=", elem.alg, "; dataqsiz=", size, "\n")
    95		}
    96		return c
    97	}
    98	
    99	// chanbuf(c, i) is pointer to the i'th slot in the buffer.
   100	func chanbuf(c *hchan, i uint) unsafe.Pointer {
   101		return add(c.buf, uintptr(i)*uintptr(c.elemsize))
   102	}
   103	
   104	// entry point for c <- x from compiled code
   105	//go:nosplit
   106	func chansend1(t *chantype, c *hchan, elem unsafe.Pointer) {
   107		chansend(t, c, elem, true, getcallerpc(unsafe.Pointer(&t)))
   108	}
   109	
   110	/*
   111	 * generic single channel send/recv
   112	 * If block is not nil,
   113	 * then the protocol will not
   114	 * sleep but return if it could
   115	 * not complete.
   116	 *
   117	 * sleep can wake up with g.param == nil
   118	 * when a channel involved in the sleep has
   119	 * been closed.  it is easiest to loop and re-run
   120	 * the operation; we'll see that it's now closed.
   121	 */
   122	func chansend(t *chantype, c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
   123		if raceenabled {
   124			raceReadObjectPC(t.elem, ep, callerpc, funcPC(chansend))
   125		}
   126		if msanenabled {
   127			msanread(ep, t.elem.size)
   128		}
   129	
   130		if c == nil {
   131			if !block {
   132				return false
   133			}
   134			gopark(nil, nil, "chan send (nil chan)", traceEvGoStop, 2)
   135			throw("unreachable")
   136		}
   137	
   138		if debugChan {
   139			print("chansend: chan=", c, "\n")
   140		}
   141	
   142		if raceenabled {
   143			racereadpc(unsafe.Pointer(c), callerpc, funcPC(chansend))
   144		}
   145	
   146		// Fast path: check for failed non-blocking operation without acquiring the lock.
   147		//
   148		// After observing that the channel is not closed, we observe that the channel is
   149		// not ready for sending. Each of these observations is a single word-sized read
   150		// (first c.closed and second c.recvq.first or c.qcount depending on kind of channel).
   151		// Because a closed channel cannot transition from 'ready for sending' to
   152		// 'not ready for sending', even if the channel is closed between the two observations,
   153		// they imply a moment between the two when the channel was both not yet closed
   154		// and not ready for sending. We behave as if we observed the channel at that moment,
   155		// and report that the send cannot proceed.
   156		//
   157		// It is okay if the reads are reordered here: if we observe that the channel is not
   158		// ready for sending and then observe that it is not closed, that implies that the
   159		// channel wasn't closed during the first observation.
   160		if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
   161			(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
   162			return false
   163		}
   164	
   165		var t0 int64
   166		if blockprofilerate > 0 {
   167			t0 = cputicks()
   168		}
   169	
   170		lock(&c.lock)
   171	
   172		if c.closed != 0 {
   173			unlock(&c.lock)
   174			panic(plainError("send on closed channel"))
   175		}
   176	
   177		if sg := c.recvq.dequeue(); sg != nil {
   178			// Found a waiting receiver. We pass the value we want to send
   179			// directly to the receiver, bypassing the channel buffer (if any).
   180			send(c, sg, ep, func() { unlock(&c.lock) })
   181			return true
   182		}
   183	
   184		if c.qcount < c.dataqsiz {
   185			// Space is available in the channel buffer. Enqueue the element to send.
   186			qp := chanbuf(c, c.sendx)
   187			if raceenabled {
   188				raceacquire(qp)
   189				racerelease(qp)
   190			}
   191			typedmemmove(c.elemtype, qp, ep)
   192			c.sendx++
   193			if c.sendx == c.dataqsiz {
   194				c.sendx = 0
   195			}
   196			c.qcount++
   197			unlock(&c.lock)
   198			return true
   199		}
   200	
   201		if !block {
   202			unlock(&c.lock)
   203			return false
   204		}
   205	
   206		// Block on the channel. Some receiver will complete our operation for us.
   207		gp := getg()
   208		mysg := acquireSudog()
   209		mysg.releasetime = 0
   210		if t0 != 0 {
   211			mysg.releasetime = -1
   212		}
   213		// No stack splits between assigning elem and enqueuing mysg
   214		// on gp.waiting where copystack can find it.
   215		mysg.elem = ep
   216		mysg.waitlink = nil
   217		mysg.g = gp
   218		mysg.selectdone = nil
   219		mysg.c = c
   220		gp.waiting = mysg
   221		gp.param = nil
   222		c.sendq.enqueue(mysg)
   223		goparkunlock(&c.lock, "chan send", traceEvGoBlockSend, 3)
   224	
   225		// someone woke us up.
   226		if mysg != gp.waiting {
   227			throw("G waiting list is corrupted")
   228		}
   229		gp.waiting = nil
   230		if gp.param == nil {
   231			if c.closed == 0 {
   232				throw("chansend: spurious wakeup")
   233			}
   234			panic(plainError("send on closed channel"))
   235		}
   236		gp.param = nil
   237		if mysg.releasetime > 0 {
   238			blockevent(mysg.releasetime-t0, 2)
   239		}
   240		mysg.c = nil
   241		releaseSudog(mysg)
   242		return true
   243	}
   244	
   245	// send processes a send operation on an empty channel c.
   246	// The value ep sent by the sender is copied to the receiver sg.
   247	// The receiver is then woken up to go on its merry way.
   248	// Channel c must be empty and locked.  send unlocks c with unlockf.
   249	// sg must already be dequeued from c.
   250	// ep must be non-nil and point to the heap or the caller's stack.
   251	func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func()) {
   252		if raceenabled {
   253			if c.dataqsiz == 0 {
   254				racesync(c, sg)
   255			} else {
   256				// Pretend we go through the buffer, even though
   257				// we copy directly. Note that we need to increment
   258				// the head/tail locations only when raceenabled.
   259				qp := chanbuf(c, c.recvx)
   260				raceacquire(qp)
   261				racerelease(qp)
   262				raceacquireg(sg.g, qp)
   263				racereleaseg(sg.g, qp)
   264				c.recvx++
   265				if c.recvx == c.dataqsiz {
   266					c.recvx = 0
   267				}
   268				c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
   269			}
   270		}
   271		if sg.elem != nil {
   272			sendDirect(c.elemtype, sg, ep)
   273			sg.elem = nil
   274		}
   275		gp := sg.g
   276		unlockf()
   277		gp.param = unsafe.Pointer(sg)
   278		if sg.releasetime != 0 {
   279			sg.releasetime = cputicks()
   280		}
   281		goready(gp, 4)
   282	}
   283	
   284	func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
   285		// Send on an unbuffered or empty-buffered channel is the only operation
   286		// in the entire runtime where one goroutine
   287		// writes to the stack of another goroutine. The GC assumes that
   288		// stack writes only happen when the goroutine is running and are
   289		// only done by that goroutine. Using a write barrier is sufficient to
   290		// make up for violating that assumption, but the write barrier has to work.
   291		// typedmemmove will call heapBitsBulkBarrier, but the target bytes
   292		// are not in the heap, so that will not help. We arrange to call
   293		// memmove and typeBitsBulkBarrier instead.
   294	
   295		// Once we read sg.elem out of sg, it will no longer
   296		// be updated if the destination's stack gets copied (shrunk).
   297		// So make sure that no preemption points can happen between read & use.
   298		dst := sg.elem
   299		memmove(dst, src, t.size)
   300		typeBitsBulkBarrier(t, uintptr(dst), t.size)
   301	}
   302	
   303	func closechan(c *hchan) {
   304		if c == nil {
   305			panic(plainError("close of nil channel"))
   306		}
   307	
   308		lock(&c.lock)
   309		if c.closed != 0 {
   310			unlock(&c.lock)
   311			panic(plainError("close of closed channel"))
   312		}
   313	
   314		if raceenabled {
   315			callerpc := getcallerpc(unsafe.Pointer(&c))
   316			racewritepc(unsafe.Pointer(c), callerpc, funcPC(closechan))
   317			racerelease(unsafe.Pointer(c))
   318		}
   319	
   320		c.closed = 1
   321	
   322		var glist *g
   323	
   324		// release all readers
   325		for {
   326			sg := c.recvq.dequeue()
   327			if sg == nil {
   328				break
   329			}
   330			if sg.elem != nil {
   331				memclr(sg.elem, uintptr(c.elemsize))
   332				sg.elem = nil
   333			}
   334			if sg.releasetime != 0 {
   335				sg.releasetime = cputicks()
   336			}
   337			gp := sg.g
   338			gp.param = nil
   339			if raceenabled {
   340				raceacquireg(gp, unsafe.Pointer(c))
   341			}
   342			gp.schedlink.set(glist)
   343			glist = gp
   344		}
   345	
   346		// release all writers (they will panic)
   347		for {
   348			sg := c.sendq.dequeue()
   349			if sg == nil {
   350				break
   351			}
   352			sg.elem = nil
   353			if sg.releasetime != 0 {
   354				sg.releasetime = cputicks()
   355			}
   356			gp := sg.g
   357			gp.param = nil
   358			if raceenabled {
   359				raceacquireg(gp, unsafe.Pointer(c))
   360			}
   361			gp.schedlink.set(glist)
   362			glist = gp
   363		}
   364		unlock(&c.lock)
   365	
   366		// Ready all Gs now that we've dropped the channel lock.
   367		for glist != nil {
   368			gp := glist
   369			glist = glist.schedlink.ptr()
   370			gp.schedlink = 0
   371			goready(gp, 3)
   372		}
   373	}
   374	
   375	// entry points for <- c from compiled code
   376	//go:nosplit
   377	func chanrecv1(t *chantype, c *hchan, elem unsafe.Pointer) {
   378		chanrecv(t, c, elem, true)
   379	}
   380	
   381	//go:nosplit
   382	func chanrecv2(t *chantype, c *hchan, elem unsafe.Pointer) (received bool) {
   383		_, received = chanrecv(t, c, elem, true)
   384		return
   385	}
   386	
   387	// chanrecv receives on channel c and writes the received data to ep.
   388	// ep may be nil, in which case received data is ignored.
   389	// If block == false and no elements are available, returns (false, false).
   390	// Otherwise, if c is closed, zeros *ep and returns (true, false).
   391	// Otherwise, fills in *ep with an element and returns (true, true).
   392	// A non-nil ep must point to the heap or the caller's stack.
   393	func chanrecv(t *chantype, c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
   394		// raceenabled: don't need to check ep, as it is always on the stack
   395		// or is new memory allocated by reflect.
   396	
   397		if debugChan {
   398			print("chanrecv: chan=", c, "\n")
   399		}
   400	
   401		if c == nil {
   402			if !block {
   403				return
   404			}
   405			gopark(nil, nil, "chan receive (nil chan)", traceEvGoStop, 2)
   406			throw("unreachable")
   407		}
   408	
   409		// Fast path: check for failed non-blocking operation without acquiring the lock.
   410		//
   411		// After observing that the channel is not ready for receiving, we observe that the
   412		// channel is not closed. Each of these observations is a single word-sized read
   413		// (first c.sendq.first or c.qcount, and second c.closed).
   414		// Because a channel cannot be reopened, the later observation of the channel
   415		// being not closed implies that it was also not closed at the moment of the
   416		// first observation. We behave as if we observed the channel at that moment
   417		// and report that the receive cannot proceed.
   418		//
   419		// The order of operations is important here: reversing the operations can lead to
   420		// incorrect behavior when racing with a close.
   421		if !block && (c.dataqsiz == 0 && c.sendq.first == nil ||
   422			c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) &&
   423			atomic.Load(&c.closed) == 0 {
   424			return
   425		}
   426	
   427		var t0 int64
   428		if blockprofilerate > 0 {
   429			t0 = cputicks()
   430		}
   431	
   432		lock(&c.lock)
   433	
   434		if c.closed != 0 && c.qcount == 0 {
   435			if raceenabled {
   436				raceacquire(unsafe.Pointer(c))
   437			}
   438			unlock(&c.lock)
   439			if ep != nil {
   440				memclr(ep, uintptr(c.elemsize))
   441			}
   442			return true, false
   443		}
   444	
   445		if sg := c.sendq.dequeue(); sg != nil {
   446			// Found a waiting sender. If buffer is size 0, receive value
   447			// directly from sender. Otherwise, receive from head of queue
   448			// and add sender's value to the tail of the queue (both map to
   449			// the same buffer slot because the queue is full).
   450			recv(c, sg, ep, func() { unlock(&c.lock) })
   451			return true, true
   452		}
   453	
   454		if c.qcount > 0 {
   455			// Receive directly from queue
   456			qp := chanbuf(c, c.recvx)
   457			if raceenabled {
   458				raceacquire(qp)
   459				racerelease(qp)
   460			}
   461			if ep != nil {
   462				typedmemmove(c.elemtype, ep, qp)
   463			}
   464			memclr(qp, uintptr(c.elemsize))
   465			c.recvx++
   466			if c.recvx == c.dataqsiz {
   467				c.recvx = 0
   468			}
   469			c.qcount--
   470			unlock(&c.lock)
   471			return true, true
   472		}
   473	
   474		if !block {
   475			unlock(&c.lock)
   476			return false, false
   477		}
   478	
   479		// no sender available: block on this channel.
   480		gp := getg()
   481		mysg := acquireSudog()
   482		mysg.releasetime = 0
   483		if t0 != 0 {
   484			mysg.releasetime = -1
   485		}
   486		// No stack splits between assigning elem and enqueuing mysg
   487		// on gp.waiting where copystack can find it.
   488		mysg.elem = ep
   489		mysg.waitlink = nil
   490		gp.waiting = mysg
   491		mysg.g = gp
   492		mysg.selectdone = nil
   493		mysg.c = c
   494		gp.param = nil
   495		c.recvq.enqueue(mysg)
   496		goparkunlock(&c.lock, "chan receive", traceEvGoBlockRecv, 3)
   497	
   498		// someone woke us up
   499		if mysg != gp.waiting {
   500			throw("G waiting list is corrupted")
   501		}
   502		gp.waiting = nil
   503		if mysg.releasetime > 0 {
   504			blockevent(mysg.releasetime-t0, 2)
   505		}
   506		closed := gp.param == nil
   507		gp.param = nil
   508		mysg.c = nil
   509		releaseSudog(mysg)
   510		return true, !closed
   511	}
   512	
   513	// recv processes a receive operation on a full channel c.
   514	// There are 2 parts:
   515	// 1) The value sent by the sender sg is put into the channel
   516	//    and the sender is woken up to go on its merry way.
   517	// 2) The value received by the receiver (the current G) is
   518	//    written to ep.
   519	// For synchronous channels, both values are the same.
   520	// For asynchronous channels, the receiver gets its data from
   521	// the channel buffer and the sender's data is put in the
   522	// channel buffer.
   523	// Channel c must be full and locked. recv unlocks c with unlockf.
   524	// sg must already be dequeued from c.
   525	// A non-nil ep must point to the heap or the caller's stack.
   526	func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func()) {
   527		if c.dataqsiz == 0 {
   528			if raceenabled {
   529				racesync(c, sg)
   530			}
   531			if ep != nil {
   532				// copy data from sender
   533				// ep points to our own stack or heap, so nothing
   534				// special (ala sendDirect) needed here.
   535				typedmemmove(c.elemtype, ep, sg.elem)
   536			}
   537		} else {
   538			// Queue is full. Take the item at the
   539			// head of the queue. Make the sender enqueue
   540			// its item at the tail of the queue. Since the
   541			// queue is full, those are both the same slot.
   542			qp := chanbuf(c, c.recvx)
   543			if raceenabled {
   544				raceacquire(qp)
   545				racerelease(qp)
   546				raceacquireg(sg.g, qp)
   547				racereleaseg(sg.g, qp)
   548			}
   549			// copy data from queue to receiver
   550			if ep != nil {
   551				typedmemmove(c.elemtype, ep, qp)
   552			}
   553			// copy data from sender to queue
   554			typedmemmove(c.elemtype, qp, sg.elem)
   555			c.recvx++
   556			if c.recvx == c.dataqsiz {
   557				c.recvx = 0
   558			}
   559			c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
   560		}
   561		sg.elem = nil
   562		gp := sg.g
   563		unlockf()
   564		gp.param = unsafe.Pointer(sg)
   565		if sg.releasetime != 0 {
   566			sg.releasetime = cputicks()
   567		}
   568		goready(gp, 4)
   569	}
   570	
   571	// compiler implements
   572	//
   573	//	select {
   574	//	case c <- v:
   575	//		... foo
   576	//	default:
   577	//		... bar
   578	//	}
   579	//
   580	// as
   581	//
   582	//	if selectnbsend(c, v) {
   583	//		... foo
   584	//	} else {
   585	//		... bar
   586	//	}
   587	//
   588	func selectnbsend(t *chantype, c *hchan, elem unsafe.Pointer) (selected bool) {
   589		return chansend(t, c, elem, false, getcallerpc(unsafe.Pointer(&t)))
   590	}
   591	
   592	// compiler implements
   593	//
   594	//	select {
   595	//	case v = <-c:
   596	//		... foo
   597	//	default:
   598	//		... bar
   599	//	}
   600	//
   601	// as
   602	//
   603	//	if selectnbrecv(&v, c) {
   604	//		... foo
   605	//	} else {
   606	//		... bar
   607	//	}
   608	//
   609	func selectnbrecv(t *chantype, elem unsafe.Pointer, c *hchan) (selected bool) {
   610		selected, _ = chanrecv(t, c, elem, false)
   611		return
   612	}
   613	
   614	// compiler implements
   615	//
   616	//	select {
   617	//	case v, ok = <-c:
   618	//		... foo
   619	//	default:
   620	//		... bar
   621	//	}
   622	//
   623	// as
   624	//
   625	//	if c != nil && selectnbrecv2(&v, &ok, c) {
   626	//		... foo
   627	//	} else {
   628	//		... bar
   629	//	}
   630	//
   631	func selectnbrecv2(t *chantype, elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
   632		// TODO(khr): just return 2 values from this function, now that it is in Go.
   633		selected, *received = chanrecv(t, c, elem, false)
   634		return
   635	}
   636	
   637	//go:linkname reflect_chansend reflect.chansend
   638	func reflect_chansend(t *chantype, c *hchan, elem unsafe.Pointer, nb bool) (selected bool) {
   639		return chansend(t, c, elem, !nb, getcallerpc(unsafe.Pointer(&t)))
   640	}
   641	
   642	//go:linkname reflect_chanrecv reflect.chanrecv
   643	func reflect_chanrecv(t *chantype, c *hchan, nb bool, elem unsafe.Pointer) (selected bool, received bool) {
   644		return chanrecv(t, c, elem, !nb)
   645	}
   646	
   647	//go:linkname reflect_chanlen reflect.chanlen
   648	func reflect_chanlen(c *hchan) int {
   649		if c == nil {
   650			return 0
   651		}
   652		return int(c.qcount)
   653	}
   654	
   655	//go:linkname reflect_chancap reflect.chancap
   656	func reflect_chancap(c *hchan) int {
   657		if c == nil {
   658			return 0
   659		}
   660		return int(c.dataqsiz)
   661	}
   662	
   663	//go:linkname reflect_chanclose reflect.chanclose
   664	func reflect_chanclose(c *hchan) {
   665		closechan(c)
   666	}
   667	
   668	func (q *waitq) enqueue(sgp *sudog) {
   669		sgp.next = nil
   670		x := q.last
   671		if x == nil {
   672			sgp.prev = nil
   673			q.first = sgp
   674			q.last = sgp
   675			return
   676		}
   677		sgp.prev = x
   678		x.next = sgp
   679		q.last = sgp
   680	}
   681	
   682	func (q *waitq) dequeue() *sudog {
   683		for {
   684			sgp := q.first
   685			if sgp == nil {
   686				return nil
   687			}
   688			y := sgp.next
   689			if y == nil {
   690				q.first = nil
   691				q.last = nil
   692			} else {
   693				y.prev = nil
   694				q.first = y
   695				sgp.next = nil // mark as removed (see dequeueSudog)
   696			}
   697	
   698			// if sgp participates in a select and is already signaled, ignore it
   699			if sgp.selectdone != nil {
   700				// claim the right to signal
   701				if *sgp.selectdone != 0 || !atomic.Cas(sgp.selectdone, 0, 1) {
   702					continue
   703				}
   704			}
   705	
   706			return sgp
   707		}
   708	}
   709	
   710	func racesync(c *hchan, sg *sudog) {
   711		racerelease(chanbuf(c, 0))
   712		raceacquireg(sg.g, chanbuf(c, 0))
   713		racereleaseg(sg.g, chanbuf(c, 0))
   714		raceacquire(chanbuf(c, 0))
   715	}
   716	

View as plain text