File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -918,4 +918,61 @@ Received: 1
918918Received: 2
919919~~~
920920
921+ ## 第六章 - goroutine 和 Go语言运行时
921922
923+ ### 工作窃取
924+
925+ Go语言将调度多个goroutine,使其在系统线程上运行。它使用的算法被称为工作窃取策略。
926+
927+ 1 . 朴素策略(公平调度策略)
928+ 在所有可用处理器之间平均分配任务。但是在fork-join模型中,任务可能会相互依赖,导致处理器空闲等待。另外还可能导致缓存的位置偏差,因为调用这些数据的任务跑在其他处理器上。
929+ 2 . 工作窃取算法:集中队列算法
930+ 使用一个集中化的FIFO队列来存储待处理的任务。处理器从队列中获取任务进行执行。但是反复进出临界区会导致较高的竞争开销。也有缓存偏移的问题,集中式队列需要频繁地加载到每个处理器的缓存中,影响缓存效率。
931+ 3 . 工作窃取算法:分布式队列算法
932+ 每个处理器拥有独立的双端队列。解决了集中式队列的竞争问题,提高了并行度和缓存命中率。每个处理器有自己的队列,减少了竞争开销。任务在同一处理器上执行,提高了缓存命中率。
933+
934+ 在goroutine开始的时候fork,join点是两个或更多的goroutine通过channel或sync包中的类型进行同步时。
935+ 工作窃取算法遵循一些基本原则。对于给定的线程:
936+
937+ 1 . 在fork点,将任务添加到与线程关联的双端队列的尾部。
938+ 2 . 如果线程空闲,则选取一个随机的线程,从它关联的双端队列头部窃取工作。
939+ 3 . 如果在未准备好的join点(即与其同步的goroutine还没有完成),则将工作从线程的双端队列尾部出栈。
940+ 4 . 如果线程的双端队列是空的,则:
941+ 1 . 暂停加入。
942+ 2 . 从随机线程关联的双端队列中窃取工作。
943+
944+ 正在执行的线程会在队列的尾部人栈或者(必要时)出栈一个任务。位于队列尾部的任务有这样几个有趣的特性:
945+
946+ * 这是最有可能完成父进程join的任务。
947+ 更快地完成join意味着我们的程序性能会更好,在内存中停留的时间更少。
948+ * 这是最有可能存在于处理器缓存中的任务。
949+ 因为这是这个线程在开始当前工作前的最后一个任务。所以当前线程执行需要的信总可能仍然存在于CPU的缓存之中。这意味着缓存的命中率更高
950+
951+ #### 窃取任务还是续体
952+
953+ 什么样的任务进行排队和窃取。fork-join模式下两种选择:新任务和续体。
954+
955+ 窃取任务是指一个空闲的处理器从另一个忙碌的处理器的任务队列中获取任务进行执行。
956+ 窃取续体是指一个处理器从另一个处理器的任务队列中获取未完成的goroutine(或称为续体)并继续执行。
957+
958+ G0语言的周度器有三个主要的概念:
959+ * G goroutine.
960+ * M OS线程(在源代码中也被称为机器)。
961+ * P 上下文(在源代码中也被称为处理器)。
962+
963+ Go语言的工作窃取算法对续体进行入队和窃取。
964+ 当一个执行线程到达一个join point时,该线程必须暂停执行,等待回调以窃取任务。
965+
966+ ### 向开发人员展示所有这些信息
967+
968+ 关键字 go 连接所有的这些
969+
970+ 在函数或闭包之前敲上go,就会有一个会自动调度的任务,它将以最有效率的方式利用它所在的机器。
971+ 作为开发者,我们依旧使用我们最熟悉的原语:function。我们不必理解新的处理方式,复杂的数据结构或调度算法。
972+
973+ ## End
974+
975+ 很好的书,有些从未见过的go代码。对于熟悉go并发的使用非常好。
976+ 最后一章关于go原理部分有些抽象,后续可能会单独写一篇关于go线程模型的文章以深入了解下。
977+
978+ 现在,该去重构我的聊天室应用了。
You can’t perform that action at this time.
0 commit comments