Why go is suitable for high concurrency
1.go scheduler: in GMP mode, OS Thread will enable other goruntines to execute when goruntine is blocked, instead of traditional multithreading blocking OSThread.
2. When using channel for communication, data can be copied directly between goruntine s, not necessarily to channel. Coprocessor A reads an empty chan, gopark itself, and then joins hchan's rec queue to wait for wake-up. When process B sends data to chan and finds sudog in the waiting queue, it will directly copy the data to the stack of process A.
channel principle:
1. There is a saying in go language called "sharing memory through communication rather than communicating through shared memory", in which channel is used for completion.
2. The structure hchan is mainly used in the channel principle. The fields in hcahn are
Where buf specifies the size of the channel buffer, which is a circular array. Sendx and recvx represent the current sending and receiving subscript positions. sendq and recvq are composed of two linked lists
It can be seen that the linked list is a two-way linked list. In each linked list, there are sudog structures. These sudog are abstract goroutine.
Source code for sending data to chan:
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { if c == nil { if !block { return false } gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2) throw("unreachable") } if debugChan { print("chansend: chan=", c, "\n") } if raceenabled { racereadpc(c.raceaddr(), callerpc, funcPC(chansend)) } // Fast path: check for failed non-blocking operation without acquiring the lock. // // After observing that the channel is not closed, we observe that the channel is // not ready for sending. Each of these observations is a single word-sized read // (first c.closed and second full()). // Because a closed channel cannot transition from 'ready for sending' to // 'not ready for sending', even if the channel is closed between the two observations, // they imply a moment between the two when the channel was both not yet closed // and not ready for sending. We behave as if we observed the channel at that moment, // and report that the send cannot proceed. // // It is okay if the reads are reordered here: if we observe that the channel is not // ready for sending and then observe that it is not closed, that implies that the // channel wasn't closed during the first observation. However, nothing here // guarantees forward progress. We rely on the side effects of lock release in // chanrecv() and closechan() to update this thread's view of c.closed and full(). if !block && c.closed == 0 && full(c) { return false } var t0 int64 if 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 { // Found a waiting receiver. We pass the value we want to send // directly to the receiver, bypassing the channel buffer (if any). send(c, sg, ep, func() { unlock(&c.lock) }, 3) return true } if c.qcount < c.dataqsiz { // Space is available in the channel buffer. Enqueue the element to send. 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 } // Block on the channel. Some receiver will complete our operation for us. gp := getg() mysg := acquireSudog() mysg.releasetime = 0 if t0 != 0 { mysg.releasetime = -1 } // No stack splits between assigning elem and enqueuing mysg // on gp.waiting where copystack can find it. mysg.elem = ep mysg.waitlink = nil mysg.g = gp mysg.isSelect = false mysg.c = c gp.waiting = mysg gp.param = nil c.sendq.enqueue(mysg) // 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. atomic.Store8(&gp.parkingOnChan, 1) gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2) // Ensure the value being sent is kept alive until the // receiver copies it out. The sudog has a pointer to the // stack object, but sudogs aren't considered as roots of the // stack tracer. KeepAlive(ep) // someone woke us up. if mysg != gp.waiting { throw("G waiting list is corrupted") } gp.waiting = nil gp.activeStackChans = false closed := !mysg.success gp.param = nil if mysg.releasetime > 0 { blockevent(mysg.releasetime-t0, 2) } mysg.c = nil releaseSudog(mysg) if closed { if c.closed == 0 { throw("chansend: spurious wakeup") } panic(plainError("send on closed channel")) } return true }
It can be seen that:
1. Sending data to the chan of nil depends on whether the block is true. If it is a chan surrounded in select, block = false, and if it is an ordinary chan, block = true. If it is true, the coprocessor will call gopark() to block and return unreachable exception information. If it is false, it will directly return false, and false is 0.
2. Receive data from the chan of nil. The process is the same as above.
3. If chan is close, panic is generated and 0 is returned
Then judge whether recvq has a waiting process. If so, directly copy the data to the process and go ready
4. The reception to the chan of the close is the same as above and returns 0
5. Receiving data from an empty chan will block, and goroutine will be abstracted as a sudog structure and added to recvq
6. Sending data to full chan will block, and goroutine is abstracted as sudog structure and added to sendq