golang解析---进程,线程,协程

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"一.背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在并发编程中进程和线程是不可忽略的两个概念,他们很好的完成了操作系统或者服务对于高并发的需求,然而随着时代的进步,协程的概念应运而生,本文旨在解释协程相对于进程和线程在高并发环境下的优势,所以会先介绍进程,线程,最后讲解协程的调度方式。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"二.详细介绍","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.1进程","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1.1概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进程基本上是一个正在执行的程序,它是操作系统中最小的资源分配单位。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1.2结构","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当一个程序被加载到内存中并成为一个进程时,它可以分为四个部分——堆栈、堆、文本和数据。下图显示了主内存中进程的简化布局:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0b/0b0056a241c559c2612565c00c827980.png","alt":"过程对比 线","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"堆栈","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进程堆栈包含临时数据,例如方法/函数参数、返回地址和局部变量。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"堆","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这是在进程运行时动态分配给进程的内存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"数据","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"包含全局变量和静态变量。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"文本","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"包括由程序计数器的值和处理器寄存器的内容表示的当前活动。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1.3进程上下文切换","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进程的上下文切换是指cpu从一个进程切换到另一个进程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进程上下文切换主要包含两个主要过程:进程地址空间切换和处理器状态切换","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"进程地址空间切换","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"切换原因","attrs":{}},{"type":"text","text":":进程地址空间指的是进程所拥有的虚拟地址空间,而这个地址空间是假的,是linux内核通过数据结构来描述出来的,从而使得每一个进程都感觉到自己拥有整个内存的假象,cpu访问的指令和数据最终会落实到实际的物理地址,对用进程而言通过缺页异常来分配和建立页表映射。进程地址空间内有进程运行的指令和数据,因此到调度器从其他进程重新切换到我的时候,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"为了保证当前进程访问的虚拟地址是自己的必须切换地址空间。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"切换方式","attrs":{}},{"type":"text","text":":将当前进程的pgd虚拟地址转换为物理地址存放在用户控件的页表基址寄存器,当访问用户空间地址的时候mmu会通过这个寄存器做遍历页表,获得物理地址。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原理是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"进程想要访问一个用户空间虚拟地址,cpu的mmu所做的工作,就是从页表基址寄存器拿到页全局目录的物理基地址,然后和虚拟地址配合来查查找页表,最终找到物理地址进行访问(当然如果tlb命中就不需要遍历页表),每次用户虚拟地址访问的时候(内核空间共享不考虑),由于页表基地址寄存器内存放的是当前执行进程的页全局目录的物理地址,所以访问自己的一套页表,拿到的是属于自己的物理地址(实际上,进程是访问虚拟地址空间的指令数据的时候不断发生缺页异常,然后缺页异常处理程序为进程分配实际的物理页,然后将页帧号和页表属性填入自己的页表条目中),就不会访问其他进程的指令和数据,这也是为何多个进程可以访问相同的虚拟地址而不会出现差错的原因","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"ps:地址空间切换过程中,还会清空tlb(页表缓存:用于存放虚拟地址映射至物理地址的标签页表条目)","attrs":{}},{"type":"text","text":",防止当前进程虚拟地址转化过程中命中上一个进程的tlb表项,一般会将所有的tlb无效,但是这会导致很大的性能损失,因为新进程被切换进来的时候面对的是全新的空的tlb,造成很大概率的tlb miss,需要重新遍历多级页表","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"处理器状态切换","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"切换原因","attrs":{}},{"type":"text","text":":需要将进程的内核栈和执行流进行切换。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"切换方式","attrs":{}},{"type":"text","text":":处理器状态切换就是将前一个进程的sp,pc等寄存器的值保存到一块内存上,然后将即将执行的进程的sp,pc等寄存器的值从另一块内存中恢复到相应寄存器中,恢复sp完成了进程内核栈的切换,恢复pc完成了指令执行流的切换。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"sp寄存器在任意时刻会保存我们栈顶的地址.","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"pc寄存器也称为程序寄存器,用于存储指向下一条指令的地址,也即将将要执行的指令代码。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.2线程","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2.1概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"线程是进程的子集,也称为轻量级进程。一个进程可以有多个线程,这些线程由调度器独立管理。一个进程内的所有线程都是相互关联的。线程是操作系统中最小的调度单位。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2.2结构","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"线程有一些公共信息,例如","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"数据段、代码段、文件等,","attrs":{}},{"type":"text","text":"这些信息共享给它们的对等线程。但包含自己的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寄存器","attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"堆栈","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"程序计数器","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"堆栈","attrs":{}},{"type":"text","text":":函数在被执行的时候产生的数据包括 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"函数参数","attrs":{}},{"type":"text","text":"、 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"局部变量","attrs":{}},{"type":"text","text":"、 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"返回地址","attrs":{}},{"type":"text","text":"等信息,这些信息是保存在栈中的,线程相当于进程中的一个执行流,为了保存执行流的信息,我们需要给线程创建独属堆栈","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寄存器","attrs":{}},{"type":"text","text":":函数运行需要额外的寄存器来保留一些信息,所以线程的寄存器也是私有的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"程序计数器","attrs":{}},{"type":"text","text":":CPU执行指令的信息保存在一个叫做程序计数器的寄存器中,通过这个寄存器我们就知道接下来要执行哪一条指令。所以线程也有自己的计数器用于告诉我们线程执行的工作顺序。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/30/304fc6c36c057baefd2b2d94bdab43d1.png","alt":"过程对比 线","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2.3线程上下文切换","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据线程的结构可知,线程没有自己的地址空间,同一进程的线程之间切换,他们共享同一进程的地址空间,所以只需要切换处理器状态;不同进程的线程之间切换,会引起进程切换","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于同一进程下的线程上下文切换不引起虚拟地址空间切换,所以它们上下文切换的花销要比进程小很多。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.3协程","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.3.1概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看作轻量级线程,他的内存占用少只要2k,且上下文切换成本低,是一个独立执行的函数,由go语言启动,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.3.2结构","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Go 中,goroutine 只不过是一个 Go 结构,包含有关正在运行的程序的信息,例如堆栈、程序计数器或其当前的 OS 线程。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"type g struct {\n\tstack stack // offset known to runtime/cgo\n\tstackguard0 uintptr // offset known to liblink\n\tstackguard1 uintptr // offset known to liblink\n\t_panic *_panic // innermost panic - offset known to liblink\n\t_defer *_defer // innermost defer\n\tm *m // current m; offset known to arm liblink\n\tsched gobuf\n\tsyscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc\n\tsyscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc\n\tstktopsp uintptr // expected sp at top of stack, to check in traceback\n\tparam unsafe.Pointer\n\tatomicstatus uint32\n\tstackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus\n\tgoid int64\n\tschedlink guintptr\n\twaitsince int64 // approx time when the g become blocked\n\twaitreason waitReason // if status==Gwaiting\n\n\tpreempt bool // preemption signal, duplicates stackguard0 = stackpreempt\n\tpreemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule\n\tpreemptShrink bool // shrink stack at synchronous safe point\n\tasyncSafePoint bool\n\n\tpaniconfault bool // panic (instead of crash) on unexpected fault address\n\tgcscandone bool // g has scanned stack; protected by _Gscan bit in status\n\tthrowsplit bool // must not split stack\n\tactiveStackChans bool\n\tparkingOnChan uint8\n\traceignore int8 // ignore race detection events\n\tsysblocktraced bool // StartTrace has emitted EvGoInSyscall about this goroutine\n\ttracking bool // whether we're tracking this G for sched latency statistics\n\ttrackingSeq uint8 // used to decide whether to track this G\n\trunnableStamp int64 // timestamp of when the G last became runnable, only used when tracking\n\trunnableTime int64 // the amount of time spent runnable, cleared when running, only used when tracking\n\tsysexitticks int64 // cputicks when syscall has returned (for tracing)\n\ttraceseq uint64 // trace event sequencer\n\ttracelastp puintptr // last P emitted an event for this goroutine\n\tlockedm muintptr\n\tsig uint32\n\twritebuf []byte\n\tsigcode0 uintptr\n\tsigcode1 uintptr\n\tsigpc uintptr\n\tgopc uintptr // pc of go statement that created this goroutine\n\tancestors *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)\n\tstartpc uintptr // pc of goroutine function\n\tracectx uintptr\n\twaiting *sudog // sudog structures this g is waiting on (that have a valid elem ptr); in lock order\n\tcgoCtxt []uintptr // cgo traceback context\n\tlabels unsafe.Pointer // profiler labels\n\ttimer *timer // cached timer for time.Sleep\n\tselectDone uint32 // are we participating in a select and did someone win the race?\n\tgcAssistBytes int64\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.3.3协程上下文切换","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"goroutine调度概念介绍","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当你的go程序启动,他会根据你的主机分配","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"逻辑处理器(P)","attrs":{}},{"type":"text","text":",每个物理核心可能有多个硬件线程,以我的电脑为例子,显示是6核但是可以出实话出来12个逻辑处理器,他们可用于并行执行OS线程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每个逻辑处理器都会分配一个","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"OS线程(M)","attrs":{}},{"type":"text","text":",该线程由操作系统管理,当go执行,有12个线程可用于执行工作,每个线程连接到一个P。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每个go程序都会有一个","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"初始的协程(G:goroutine)","attrs":{}},{"type":"text","text":",他可以被看作是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"程序级别线程","attrs":{}},{"type":"text","text":",所有的goroutine在M上进行上下文切换。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后还需要有","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"运行队列","attrs":{}},{"type":"text","text":",全局运行队列(GRQ)和本地运行队列(LRQ)。每个 P 都有一个 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LRQ,用于管理分配在 P 上下文中执行的 Goroutine","attrs":{}},{"type":"text","text":"。这些 Goroutine 轮流在分配给该 P 的 M 上进行上下文切换。GRQ 用于管理尚未分配给P的 Goroutine。有一个将 Goroutines 从 GRQ 移动到 LRQ 的过程,我们将在后面讨论。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/11/11413e494428c2540e349637f34bbec3.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进程,线程的切换,都是操作系统进行调度的,go调度是go语音的一部分,它运行在内核之上的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用户空间中","attrs":{}},{"type":"text","text":"。它不是抢占式,而是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"协作调度","attrs":{}},{"type":"text","text":"。作为协作调度程序意味着调度程序需要在代码中的安全点发生的明确定义的用户空间事件来做出调度决策。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上是goroutine的定义,从上文可知,goroutine调度与进程线程最大的区别就在于它是运行在用户空间中的协作调度方式的上下文切换。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"会触发调度程序调度决策的场景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.go关键字使用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.垃圾回收","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.系统调用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.mutex,channel调用导致goroutine阻塞","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"切换开销","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Goroutine上下文切换只涉及修改三个寄存器(PC[程序寄存器]/SP[堆栈指针]/DX)的值,而比较线程的上下文切换需要包括模式切换(从用户态切换到内核态)和16个寄存器,PC、SP等寄存器刷新","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"三.总结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"进程上下文切换开销:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.地址空间","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.硬件上下文","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"线程上下文切换开销:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.硬件上下文","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.同一进程下不切换地址空间","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"goroutine切换开销:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.用户态,不用象线程和进程一样多进行一次内核用户态切换","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.只需要保存/恢复三个寄存器的值,开销远远小于线程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其余优点:goroutine的栈空间为2k,线程为2m,进程是10m","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由进程,线程,goroutine的上下文切换可以明显看出是一个逐步减负的过程,这个过程可以结合它们的结构来理解,coverco故而自带goroutine的go语言在高并发开发中有着得天独厚的优势。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"参考文章","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.toptal.com/software/introduction-to-concurrent-programming","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.tutorialspoint.com/operating_system/os_processes.htm","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.guru99.com/process-management-pcb.html","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://blog.csdn.net/21cnbao/article/details/108860584","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"线程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.javatpoint.com/process-vs-thread","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://blog.csdn.net/weixin_39630048/article/details/113328415","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"协程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.youtube.com/watch?v=f6kdp27TYZs","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://talks.golang.org/2012/concurrency.slide#13(关于并发演讲)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://morioh.com/p/36af32e3f52c","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章