goroutine是什么 单线程跑多少个

从零开始学习c语言Goroutine背后的系统常识_expressway教程网
           
从零开始学习c语言Goroutine背后的系统常识
上一节尾要说的是并收和并行的概念,而线程是最直不雅的并收的真现,那一节我们尾要说操作系统若何让多个线程并收的履行,固然正在多CPU的时间,也就是并行的履行。我们不会商历程,历程的意义是“断绝的履行”,而不是“零丁的履行序列”。 2. 多CPU。不论
  上一节尾要说的是并收和并行的概念,而线程是最直不雅的并收的真现,那一节我们尾要说操作系统若何让多个线程并收的履行,固然正在多CPU的时间,也就是并行的履行。我们不会商历程,历程的意义是“断绝的履行”,而不是“零丁的履行序列”。  2. 多CPU。不论是真的多颗CPU仍是多核仍是超线程,总之我们有了多个CPU流水线。  (1) 若是某个使命收动了一个系统挪用,比圆长工夫期待IO,那当火线程就被内核放进了期待调剂的队列,岂不是让其他使命都出有时机履行?  从操作系统层里供给协程和其并行调剂的,如同只要OS X和iOS的Grand Central Dispatch,其年夜部门功效也是正在运转库里真现的。  以上年夜概先容了一个用多线程来真现并收的法式是若何被操作系统调剂和并行履行(正在有多个逻辑处置器时),同时年夜家也能够看到,代码片断或说逻辑控造流的调剂和切换真正在其真不神秘,理论上,我们也能够不依靠操作系统和其供给的线程,正在本人法式的代码段里界说多个片断,然后正在我们本人法式里对其停止调剂和切换。  并且不难理解:  1. 撑持新的语义或语法,从而便于我们描写和办理题目。比圆Java的泛型、Annotation、lambda表达式。  5. goroutine  3. 对系统挪用有了更杰出更全里的封拆,使我们可以做到之前正在那个说话里做不到或很难做到的工作。比圆Java NIO  “运转库”那个词真正在不止包罗用于和编译后的目的履行法式停止链接的库文件,也包罗了剧本说话或字节码诠释型说话的运转,比圆Python,C#的CLR,Java的JRE。  并行是指法式的运转状况。若是一个法式正在某一时候被多个CPU流水线同时停止处置,那末我们就说那个法式是以并行的情势正在运转。(严酷意义上讲,我们不克不及说某法式是“并行”的,由于“并行”不是描写法式自己,而是描写法式的运转状况,但那篇小文里就不那末句斟字嚼,以下说到“并行”的时间,就是指代 “以并行的情势运转”)隐然,并行必定是需要硬件撑持的。  Go说话经过goroutine供给了今朝为止所有(我所领会的)说话里对并收编程的最清楚最直接的撑持,Go说话的文档里对其特征也描写的十分全里乃至跨越了,正在那里,基于我们的系统常识先容,罗列一下goroutine的特征,算是小结:  以上就是最简化的一个并收框架的设计思索,正在我们现真开辟事情中碰到的并收框架大概果为说话和运转库的差别而有所差别,正在功效和易用性上也大概各有弃取,但底层的道理都是殊途同回。  所以,正在我们说“某某说话新增了某某功效”的时间,凡是是是那么几种大概:  1. 隐式地界说并触收多个代码片断,也就是逻辑控造流,由利用法式或操作系统对它们停止调剂。它们可所以无闭的,也可所以彼此依靠需要交互的,比圆提到的素数计较,真正在它也是个经典的出产者和消费者的题目:两个逻辑控造流A和B,A收生输出,当有了输出后,B获得A的输出停止处置。线程只是真现并收的此中一个脚腕,除此以中,运转库或是利用法式自己也有多种脚腕来真现并收,那是下节的尾要内容。  线程是操作系统对中供给的办事,利用法式可以经过系统挪用让操作系统启动线程,并负责随后的线程调剂和切换。我们先思索单颗单核CPU,操作系统内核与利用法式真正在是也是正在同享统一个CPU,当EIP正在利用法式代码段的时间,内核并出有控造权,内核其真不是一个历程或线程,内核只是以真形式运转的,代码段权限为RING 0的内存中的法式,只要当收生间断或是利用法式呼唤系统挪用的时间,控造权才转移到内核,正在内核里,所有代码都正在统一个地点空间,为了给差别的线程供给办事,内核会为每个线程成立一个内核货仓,那是线程切换的闭头。凡是是,内核会正在时钟间断里或系统挪用返回前(思索到机能,凡是是是正在不频仍产生的系统挪用返回前),对全部系统的线程停止调剂,计较当火线程的盈余工夫片,若是需要切换,就正在“可运转”的线程队列里计较优先级,选出目的线程后,则保留当火线程的运转,并恢复目的线程的运转,此中最主要的,就是切换货仓指针ESP,然后再把EIP指向目的线程前次被移出CPU时的指令。Linux内核正在真现线程切换时,耍了个花,它其真不是直接JMP,而是先把ESP切换为目的线程的内核栈,把目的线程的代码地点压栈,然后JMP到__switch_to(),相当于真造了一个CALL __switch_to()指令,然后,正在__switch_to()的最后利用RET指令返回,如许就把栈里的目的线程的代码地点放进了EIP,接下来CPU就开端履行目的线程的代码了,真正在也就是前次停正在switch_to那个宏睁开的处所。  (7) Go说话运转库封拆了同步IO,所以可以写出貌似并收数良多的办事端,可纵然我们经过调整$GOMAXPROCS来充真使用多核CPU并行处置,其效力也不如我们使用IO事务驱动设计的、依照事件类型划分好开适比例的线程池。正在响应工夫上,合作式调剂是硬伤。  4. 并收编程框架  2. 隐式地放置多个代码片断,正在系统事务产生时触收履行响应的代码片断,也就是事务驱动的体例,比圆某个端心或管道吸支到了数据(多IO的环境下),再比圆历程吸支到了某个旌旗灯号(signal)。  并行可以正在四个层里上做到:  很隐然,若是是多线程的话,我们需要经过线程库或系统挪用供给的同步机造来对那个构造体内数据的拜候。  1. 操作系统与运转库  对系统挪用的封拆只是运转库的很小一部门功效,运转库凡是是还供给了诸如字符串处置、数学计较、经常使用数据构造容器等等不需要操作系统撑持的功效,同时,运转库也会对操作系统撑持的功效供给更容易用更高级的封拆,比圆带缓存和花式的IO、线程池。  (5) 和所有其他并收框架里的协程一样,goroutine里所谓“无锁”的长处只正在单线程下有用,若是$GOMAXPROCS 1而且协程间需要通讯,Go运转库会负责加锁数据,那也是为何sieve.go如许的例子正在多CPU多线程时反而更缓的缘由  正在其他说话里,我们所谓的“使命”更多时间被称为“协程”,也就是Coroutine。比圆C++里最经常使用的是 Boost.Coroutine;Java由于有一层字节码诠释,比力贫苦,但也有撑持协程的JVM补钉,或是动态点窜字节码以撑持协程的项目;PHP和 Python的generator和yield真正在已是协程的撑持,正在此之上可以封拆出更通用的协程接心和调剂;别的还有本生撑持协程的Erlang 等,笔者不懂,就不说了,详细可拜睹Wikipedia的页里:  【编纂保举】  3. 并收只是更契开真际题目本量的表达体例,并收的最初目标是简化代码逻辑,而不是使法式运转的更快;  5. goroutine  1. 多台机械。天然我们就有了多个CPU流水线,比圆Hadoop集群里的MapReduce使命。  正在单线程的环境下,我们可以界说一个构造,此中有变量用于寄存交互数据自己,和数据确当前可用状况,和负责读写此数据的两个使命的编号。然后我们的并收编程框架再供给read和write圆式供使命挪用,正在read圆式里,我们轮回查抄数据是不是可用,若是数据还弗成用,我们就挪用 schedule()让出CPU进进期待;正在write圆式里,我们往构造里写进数据,变动数据可用状况,然后返回;正在schedule()里,我们查抄数据可用状况,若是可用,则激活需要读取此数据的使命,该使命继续轮回检测数据是不是可用,收现可用,读取,变动状况为弗成用,返回。代码的简单逻辑以下:  若是我们采取多线程来机闭我们全部的法式,那末我们可以封拆系统挪用的接心,当某个使命进进系统挪用时,我们就把当火线程留给它(临时)独享,并新的线程来处置其他使命。  此中,1牵扯到集布式处置,包罗数据的集布和使命的同步等等,并且是基于收集的。3和4凡是是是编译器和CPU的开辟职员需要思索的。那里我们说的并行尾要针对第2种:单台机械内的多核CPU并行。  Go说话从降生到普及已三年了,先行者年夜都是Web开辟的布景,也有了一些普及型的册本,可系统开辟布景的人正在进修那些册本的时间,总有语焉不详的觉得,网上也有若干传播甚广的文章,可此中或多或少总有些与事真不符的手艺描写。希看那篇文章能为比力贫累系统编程布景的Web开辟职员先容一下goroutine背后的系统常识。  3. 线程的调剂  1. 操作系统与运转库  (1) 我们运转正在用户态,是出有间断或系统挪用如许的机造来挨断代码履行的,那末,一旦我们的schedule()代码把控造权交给了使命的代码,我们下次的调剂正在甚么时间产生?谜底是,不会产生,只要靠使命自动挪用schedule(),我们才有时机停止调剂,所以,那里的使命不克不及像线程一样依靠内核调剂从而毫无的履行,我们的使命里必定要隐式的挪用schedule(),那就是所谓的合作式(cooperative)调剂。(固然我们可以经过注册旌旗灯号处置函数来摹拟内核里的时钟间断并获得控造权,可题目正在于,旌旗灯号处置函数是由内核挪用的,正在其完毕的时间,内核从头取得控造权,随后返回用户态并继续沿着旌旗灯号产生时被间断的代码径履行,从而我们出法正在旌旗灯号处置函数内停止使命切换)  对通俗的电脑用户来讲,能理解利用法式是运转正在操作系统之上就充足了,可对开辟者,我们还需方法会我们写的法式是若何正在操作系统之上运转起来的,操作系统若何为利用法式供给办事,如许我们才能分清晰哪些办事是操作系统供给的,而哪些办事是由我们所利用的说话的运转库供给的。  并收可以经过以下体例做到:  AD:  到那里,我们年夜概知道了若何机闭一个并收的编程框架,可若何让使命可以并行的正在多个逻辑处置器上履行呢?只要内核才有调剂CPU的权限,所以,我们仍是必需经过系统挪用建立线程,才可以真现并行。正在多线程处置多使命的时间,我们还需要思索几个题目:  除内存办理、文件办理、历程办理、中设办理等等内部模块之中,操作系统还供给了很多内部接供词利用法式利用,那些接心就是所谓的“系统挪用”。从DOS期间开端,系统挪用就是经过硬间断的情势来供给,也就是闻名的INT 21,法式把需要挪用的功效编号放进AH存放器,把参数放进其他指定的存放器,然后挪用INT 21,间断返回后,法式从指定的存放器(凡是是是AL)里获得返回值。如许的做法一向到飞跃2也就是P6出来之前都出有变,比圆windows经过INT 2E供给系统挪用,Linux则是INT 80,只不中厥后的存放器比之前年夜一些,并且大概再多一层跳转表查询。厥后,Intel和AMD划分供给了效力更高的SYSENTER/SYSEXIT和SYSCALL/SYSRET指令来取代之前的间断体例,略过了耗时的级别查抄和存放器压栈出栈的操作,直接完成从RING 3代码段到RING 0代码段的转换。  (2) goroutine就是一段代码,一个函数进心,和正在堆上为其分派的一个货仓。所以它十分便宜,我们可以很轻松的建立上万个goroutine,但它们其真不是被操作系统所调剂履行  对开辟者,我们还需方法会我们写的法式是若何正在操作系统之上运转起来的,操作系统若何为利用法式供给办事,如许我们才能分清晰哪些办事是操作系统供给的,而哪些办事是由我们所利用的说话的运转库供给的。  我们起尾需要理解IA-32 CPU的指令控造体例,如许才能理解若何正在多个指令序列(也就是逻辑控造流)之间停止切换。CPU经过CS:EIP存放器的值肯定下一条指令的,然则 CPU其真不许可直接利用MOV指令来变动EIP的值,必需经过JMP系列指令、CALL/RET指令、或INT间断指令来真现代码的跳转;正在指令序列间切换的时间,除变动EIP以中,我们还要代码大概会利用到的各个存放器的值,特别是栈指针SS:ESP,和EFLAGS标记位等,都可以或许恢复到目的指令序列前次履行到那个时间的状况。  但是,我们写法式其真不需要往挪用间断或是SYSCALL指令,那是由于操作系统供给了一层封拆,正在Windows上,它是NTDLL.DLL,也就是常说的Native API,我们不单不需要往直接挪用INT 2E或SYSCALL,精确的说,我们不克不及直接往挪用INT 2E或SYSCALL,由于Windows并出有公然其挪用范例,直接利用INT 2E或SYSCALL出法将来的兼容性。正在Linux上则出有那个题目,系统挪用的列表都是公然的,并且Linus十分注重兼容性,不会往做任何变动,glibc里乃至专门供给了syscall(2)来便利用户直接用编号挪用,不中,为领会决glibc和内核之间差别版本兼容性带来的贫苦,和为了进步某些挪用的效力(比圆__NR_ gettimeofday),Linux上仍是对部门系统挪用做了一层封拆,就是VDSO(初期叫linux-gate.so)。  那几段稍微抽象,我们可以用一个最简单的例子来把那些概念真例化:用C说话写一个最简单的HelloWorld,它就并收的,若是我们成立多个线程,每一个线程里挨印一个HelloWorld,那末那个法式就是并收的,若是那个法式运转正在老式的单核CPU上,那末那个并收法式还不是并行的,若是我们用多核多CPU且撑持多使命的操作系统来运转它,那末那个并收法式就是并行的。  2. 并收不是并行的充真前提,一个并收的法式,若是只被一个CPU流水线停止处置(经过度时),那末它就不是并行的。  正在CM从零开始学习c语言Goroutine背后的系统常识U那本闻名的《Computer Systems: A Programmers Perspective》里的那张图也十分直不雅清楚:  2. 并收与并行 (Concurrency and Parallelism)  498)this.width=498; onmousewheel = javascript:return big(this) alt= src=/files/uploadimg//.png />  那里需要弥补几点:(1) 固然IA-32供给了TSS (Task State Segment),试图简化操作系统停止线程调剂的流程,但果为其效力低下,并且其真不是通用尺度,倒霉于移植,所以支流操作系统都出有往使用TSS。更严酷的说,真正在仍是用了TSS,由于只要经过TSS才能把货仓切换到内核货仓指针SS0:ESP0,但除此以中的TSS的功效就完整出有被利用了。(2) 线程从用户态进进内核的时间,相干的存放器和用户态代码段的EIP已保留了一次,所以,正在所说的内核态线程切换时,需要保留和恢复的内容其真不多。 (3) 以上描写的都是抢占式(preemptively)的调剂体例,内核和此中的硬件驱动也会正在期待内部资本可用的时间自动挪用schedule(),用户态的代码也能够经过sched_yield()系统挪用自动收动调剂,让出CPU。  (4) goroutine是合作式调剂的,若是goroutine会履行很长工夫,并且不是经过期待读取或写进channel的数据来同步的话,就需要自动挪用Gosched()来让出CPU  现正在我们一台通俗的PC或办事里凡是是都有多颗CPU (physical package),每颗CPU又有多个核 (processor core),每一个核又可以撑持超线程 (two logical processors for each core),也就是逻辑处置器。每一个逻辑处置器都有本人的一套完备的存放器,此中包罗了CS:EIP和SS:ESP,从而,以操作系统和利用的角度来看,每一个逻辑处置器都是一个零丁的流水线。正在多处置器的环境下,线程切换的道理和流程真正在和单处置器时是根本分歧的,内核代码只要一份,当某个CPU上产生时钟间断或是系统挪用时,该CPU的CS:EIP和控造权又回到了内核,内核按照调剂战略的后果停止线程切换。但正在那个时间,若是我们的法式用线程真现了并收,那末操作系统可使我们的法式正在多个CPU上真现并行。  (3) 除被系统挪用梗阻的线程中,Go运转库最多会启动$GOMAXPROCS个线程来运转goroutine  本文链接:  闭于并收与并行的题目,Go说话的作者Rob Pike专门就此写过一个幻灯片:  看起来很简单啊,但是我们还需要办理几个题目:  (2) 货仓。和内核调剂线程的道理一样,我们也需要为每一个使命零丁分派货仓,而且把其货仓信息保留正在使命属性里,正在使命切换时也保留或恢复当前的SS:ESP。使命货仓的空间可所以正在当火线程的货仓上分派,也可所以正在堆上分派,但凡是是是正在堆上分派比力好:险些出丰年夜小或使命总数的、货仓年夜小可以动态扩大 (gcc有split stack,但太复纯了)、便于把使命切换到其他线程。  2. 并收与并行 (Concurrency and Parallelism)  并收是指法式的逻辑构造。非并收的法式就是一根竹竿捅到底,只要一个逻辑控造流,也就是挨次履行的(Sequential)法式,正在任什么时候刻,法式只会处正在那个逻辑控造流的某个。而若是某个法式有多个的逻辑控造流,也就是可以同时处置(deal)多件工作,我们就说那个法式是并收的。那里的 “同时”,并纷歧定如果真正正在时钟的某一时候(那是运转状况而不是逻辑构造),而是指:若是把那些逻辑控造流画成时序流程图,它们正在工夫线上是可以堆叠的。  (1) goroutine是Go说话运转库的功效,不是操作系统供给的功效,goroutine不是用线程真现的。详细可拜睹Go说话源码里的pkg/runtime/proc.c  和内核的真现相似,只是我们不需要思索间断和系统挪用,那末,我们的法式本量上就是一个轮回,那个轮回自己就是调剂法式schedule(),我们需要一个使命的列表,按照我们界说的战略,进步前辈先出或是有优先级等等,每次从列遴选出一个使命,然后恢复各个存放器的值,而且JMP到该使命前次被停息的处所,所有那些需要保留的信息都可以作为该使命的属性,寄存正在使命列。  但是,我们写法式也很少直接挪用NTDLL或VDSO,而是经过更上一层的封拆,那一层处置了参数筹办和返回值花式转换、和得足处置和毛病代码转换,那就是我们所利用说话的运转库,对C说话,Linux上是glibc,Windows上是kernel32(或挪用msvcrt),对其他说话,比圆Java,则是JRE,那些“其他说话”的运转库凡是是末究仍是挪用glibc或kernel32。  为了描写便利,我们接下来把“代码片断”称为“使命”。  3. 单CPU核里的ILP(Instruction-level parallelism),指令级并行。经过复纯的造造工艺和对指令的剖析和分支展看和治序履行,现正在的CPU可以正在单个时钟周期内履行多条指令,从而,纵然并收的法式,也大概是以并行的情势履行。  正在单线程的环境下,我们只要一个办理法子,就是利用非梗阻的IO系统挪用,并让出CPU,然后正在schedule()里同一停止轮询,稀有据时切换回该fd对应的使命;效力略低的做法是一直止同一轮询,让各个使命正在轮到本人履行时再次用非梗阻体例停止IO,直到稀有据可用。  (6) Web等办事端法式要处置的哀求从本量上来说是并行处置的题目,每一个哀求根本,互不依靠,险些出稀有据交互,那不是一个并收编程的模子,而并收编程框架只是办理了其语义表述的复纯性,其真不是从底子上进步处置的效力,或许是并收毗连和并收编程的英文都是concurrent吧,很轻易收生“并收编程框架和coroutine可以高效处置年夜量并收毗连”的。  果为保留和恢复使命履行状况需要拜候CPU存放器,所以相干的运转库也都市列出所撑持的CPU列表。  (2) 使命同步。比圆我们上节提到的出产者和消费者的例子,若何让消费者正在数据还出有被出产出来的时间进进期待,而且正在数据可用时触收消费者继续履行呢?  但任何一门说话,包罗其运转库和运转,都不大概缔造出操作系统不撑持的功效,Go说话也是如许,不管它的特征描写看起来何等炫丽,那必定都是其他说话也能够做到的,只不中Go供给了更便利更清楚的语义和撑持,进步了开辟的效力。  那里也需要弥补两点:(1) 多核的场景里,各个核之间其真不是完整对等的,比圆正在统一个核上的两个超线程是同享L1/L2缓存的;正在有NUMA撑持的场景里,每一个核拜候内存差别地区的延早是纷歧样的;所以,多核场景里的线程调剂又引进了“调剂域”(scheduling domains) 的概念,但那不影响我们理解线程切换机造。(2) 多核的场景下,间断收给哪个CPU?硬间断(包罗除以0,缺页非常,INT指令)天然是正在触收该间断的CPU上收生,而硬间断则又分两种环境,一种是每一个 CPU本人收生的间断,比圆时钟,那是每一个CPU处置本人的,还有一种是内部间断,比圆IO,可以经过APIC来指定其送给哪个CPU;由于调剂法式只能控造当前的CPU,所以,若是IO间断出有停止平均的分派的话,那末和IO相干的线程就只能正在某些CPU上运转,致使CPU负载不均,进而影响全部系统的效力。  2. 供给了新的对象或类库,削减了我们开辟的代码量。比圆Python 2.7的argparse  还有一个稍微复纯的例子,更能申明并收纷歧定可以并行,并且并收不是为了效力,就是Go说话例子里计较素数的sieve.go。我们从小到年夜针对每个果子启动一个代码片断,若是当前考证的数能被当前果子除尽,则该数不是素数,若是不克不及,则把该数收送给下一个果子的代码片断,直到最后一个果子也出法除尽,则该数为素数,我们再启动一个它的代码片断,用于考证更年夜的数字。那是契开我们计较素数的逻辑的,并且每一个果子的代码处置片断都是沟通的,所以法式十分的简练,但它出法被并行,由于每一个片断都依靠于前一个片断的处置后果和输出。  比圆,glic里的getcontext/setcontext/swapcontext系列库函数可以便利的用来保留和恢复使命履行状况;Windows供给了Fiber系列的SDK API;那两者都不是系统挪用,getcontext和setcontext的man page固然是正在section 2,但那只是SVR4时的汗青遗留题目,真正在现代码是正在glibc而不是kernel;CreateFiber是正在kernel32里供给的,NTDLL里并出有对应的NtCreateFiber。  1. 并收是并行的需要前提,若是一个法式自己就不是并收的,也就是只要一个逻辑控造流,那末我们不大概让其被并行处置。  (8) goroutine最年夜的价值是真正在现了并收协程和现真并行履行的线程的映照和动态扩大,跟着其运转库的不停成长和美谦,其机能必定会愈来愈好,特别是正在CPU核数愈来愈多的将来,末有一天我们会为了代码的简练和可性而扔却那一点点机能的不同。  4. 单指令多半据(Single instruction, multiple data. SIMD),为了多半据的处置,现正在的CPU的指令集撑持单条指令对多条数据停止操作。  4. 并收编程框架  3. 线程的调剂  系统挪用都供给甚么功效呢?用操作系统的名字加上对应的间断编号到谷歌上一查便可以获得完备的列表 (Windows,Linux),那个列表就是操作系统和利用法式之间相同的和谈,若是需要超越此和谈的功效,我们就只能正在本人的代码里往真现,比圆,对内存办理,操作系统只供给历程级此中内存段的办理,比圆Windows的virtualmemory系列,或是Linux的brk,操作系统不会往正在意利用法式若何为新建对象分派内存,或是若何做垃圾支受接管,那些都需要利用法式本人往真现。若是超越此和谈的功效出法本人真现,那我们就说该操作系统不撑持该功效,举个例子,Linux正在2.6之前是不撑持多线程的,不管若何正在法式里摹拟,我们都出法做出多个可以同时运转的并契开POSIX 1003.1c语义尺度的调剂单位。
上一篇:  下一篇:
赞助商信息下次自动登录
现在的位置:
& 综合 & 正文
Go学习笔记——go routine
Go routine indeed
本短结论引用自:,让我了解为什么goroutine这么轻量级,以及其优势劣势。
Go语言通过goroutine提供了目前为止所有(我所了解的)语言里对于并发编程的最清晰最直接的支持,Go语言的文档里对其特性也描述的非常全面甚至超过了,在这里,基于我们上面的系统知识介绍,列举一下goroutine的特性,算是小结:
(1) goroutine是Go语言运行库的功能,不是操作系统提供的功能,goroutine不是用线程实现的。具体可参见Go语言源码里的pkg/runtime/proc.c
(2) goroutine就是一段,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行
(3) 除了被系统调用阻塞的线程外,Go运行库最多会启动$GOMAXPROCS个线程来运行goroutine
(4) goroutine是协作式调度的,如果goroutine会执行很长时间,而且不是通过等待读取或写入channel的数据来同步的话,就需要主动调用Gosched()来让出CPU
(5) 和所有其他并发框架里的协程一样,goroutine里所谓“无锁”的优点只在单线程下有效,如果$GOMAXPROCS & 1并且协程间需要通信,Go运行库会负责加锁保护数据,这也是为什么sieve.go这样的例子在多CPU多线程时反而更慢的原因
(6) Web等服务端要处理的请求从本质上来讲是并行处理的问题,每个请求基本独立,互不依赖,几乎没有数据交互,这不是一个并发编程的模型,而并发编程框架只是解决了其语义表述的复杂性,并不是从根本上提高处理的效率,也许是并发连接和并发编程的英文都是concurrent吧,很容易产生“并发编程框架和coroutine可以高效处理大量并发连接”的误解。
(7) Go语言运行库封装了异步IO,所以可以写出貌似并发数很多的服务端,可即使我们通过调整$GOMAXPROCS来充分利用多核CPU并行处理,其效率也不如我们利用IO事件驱动设计的、按照事务类型划分好合适比例的线程池。在响应时间上,协作式调度是硬伤。
(8) goroutine最大的价值是其实现了并发协程和实际并行执行的线程的映射以及动态扩展,随着其运行库的不断发展和完善,其性能一定会越来越好,尤其是在CPU核数越来越多的未来,终有一天我们会为了代码的简洁和可维护性而放弃那一点点性能的差别。
启动一个go routine
go关键字+函数名即可启动一个go routine:
package main
func p() {
for i := 0; i & 100; i++ {
fmt.Println(i)
time.Sleep(time.Second * 1)
func main() {
var input string
fmt.Scanln(&input)
fmt.Println("End")
goroutine通信:Channel
go routine使用channel来进行routine间的通信:
package main
"math/rand"
func sell(c chan int) {
num := &- c
fmt.Println("Sell ",num," bread")
func produce(c chan int){
num := rand.Intn(10)
t := time.Duration(num)
fmt.Println("Product ",num," bread")
time.Sleep(time.Second* t)
func main() {
var c chan int = make(chan int)
go sell(c)
go produce(c)
var input string
fmt.Scanln(&input)
fmt.Println("End")
//输出结果:
默认channel是双向的,在函数入口也可以定义为单向:
func sell(c &-chan int)
//只能从通道取出
func produce(c chan&- int) //通道只能放入
select语句用于在多个channel中选择已经ready的通道,如:
case msg1 := &- c1:
fmt.Println("Message 1", msg1)
case msg2 := &- c2:
fmt.Println("Message 2", msg2)
case &- time.After(time.Second):
fmt.Println("timeout")
fmt.Println("nothing ready")
time.After会在指定时间后创建一个匿名通道,用来进行等待超时。如果所有channel都没有准备妥当,则立即执行default块。
在make channel时指定第二个参数可以创建一个缓冲通道,类似其他高级语言中的定长队列:
c := make(chan int, 1)
&&&&推荐文章:
【上篇】【下篇】}

我要回帖

更多关于 goroutine是什么 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信