golang中各種並發模式看起來是怎麼樣的?下面這篇文章就透過20 張動圖為你示範 一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 並發,希望對大家有幫助!
如果你更喜歡透過影片了解本文,請點擊觀看我在一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】pherCon上的演講 www.youtube.com/watch? v=KyuFeiG3Y6...
一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】語言最強大的特性之一就是基於Tony Hoare's CSP 這篇論文實現的內建並發. 一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】在設計時就考慮了並發並允許我們建立複雜的並發管道。那你有沒有想過,各種並發模式看起來是怎麼樣的呢?
你一定想過。我們多數情況下都會透過想像來思考問題. 如果我問你一個關於「1到100的數字」的問題,你腦子裡就會下意識的出現一系列畫面。例如,我會把它想像成一條從我開始的直線,從數字1到20然後右轉90度一直到1000 。我記得我很小的時候,在我們的幼兒園裡,衣帽間裡有很多數字,寫在牆上,數字20剛好在轉角處。你可能有你自己的關於數字的畫面。另一個常見的例子是一年四季的視覺展現──有人將之想像成一個盒子,有人將之想像成一個圈。
無論如何, 我想用一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】和WebGL把我對於常見的並發模式的具象化嘗試展現給大家.這多多少少代表了我對於並發程序的理解。如果能聽到我和大家腦海中的畫面有什麼不同,一定會非常有趣。我特別想知道 Rob Pike 或 Sameer Ajmani 腦子裡是怎麼描繪並發圖像的. 我打賭我會很感興趣的。 【相關推薦:一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】影片教學】
那麼,讓我們從一個很基礎的「Hello,Concurrent World」範例開始我們今天的主題。
#程式碼很簡單-單一通道,單一goroutine,單次寫入,單次讀取。
package main func main() { // 创建一个int类型的通道 ch := make(chan int) // 开启一个匿名 goroutine go func() { // 向通道发送数字42 ch <p><a href="https://divan.dev/demos/hello/" target="_blank">轉到互動式WebGL 動畫</a> <img src="https://img.php.cn/upload/article/000/000/024/03104ecc960e1992d7b04ec4cd79a77f-0.gif" alt="Hello, World"></p><p>藍色線代表隨時間運行的goroutine. 連接'main'和'go #19'的藍色細線用來標記goroutine的開始和結束同時展示了父子關係,最後,紅線代表發送/接收動作. 雖然這是兩個獨立的動作,我還是嘗試用“從A 發送到B”的動畫將他們表示成一個動作. goroutine 名稱中的“#19” 是goroutine 真實的內部ID, 其獲取方法參考了Scott Mansfield 的“一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】routine IDs” 這篇文章。 </p><h3> <span class="header-link octicon octicon-link"></span><strong><span style="font-size: 18px;">Timers</span></strong> </h3><p>#實際上,你可以透過以下方法建立一個簡單的計時器-建立一個通道, 開啟一個goroutine 讓其在指定的時間間隔後向通道中寫入數據,然後將這個通道傳回給呼叫者。於是呼叫函數就會在讀取通道時阻塞,直到先前設定的時間間隔過去。接下來我們呼叫24次計時器然後嘗試具象化呼叫過程。 </p><pre class="brush:php;toolbar:false">package main import "time" func timer(d time.Duration) <p><a href="https://divan.dev/demos/timers/" target="_blank">前往互動式 WebGL 動畫</a> <img src="https://img.php.cn/upload/article/000/000/024/6e2b2d910982ba58209c1c53cd9c7f6d-1.gif" alt="Recurrent Timers"></p><p>很整潔,對嗎? 我們繼續。 </p><h3> <span class="header-link octicon octicon-link"></span><strong><span style="font-size: 18px;">Ping-pong</span></strong> </h3><p>#這個並發範例取自一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】ogle員工Sameer Ajmani 「<a href="https://talks.golang.org/2013/advconc.slide#1" target="_blank" textvalue="Advanced 一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 Concurrency Patterns">Advanced 一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 Concurrency Patterns </a>” 演講。當然,這個模式不算非常高級,但是對於那些只熟悉一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】的並發機制的人來說它看起來可能非常新鮮有趣。 </p><p>這裡我們用一個通道代表乒乓球檯. 一個整數變數代表球, 然後用兩個goroutine代表玩家,玩家透過增加整數變數的值(點擊計數器)模擬擊球動作。 </p><pre class="brush:php;toolbar:false">package main import "time" func main() { var Ball int table := make(chan int) go player(table) go player(table) table <p><a href="https://divan.dev/demos/pingpong/" target="_blank" textvalue="转到交互式 WebGL 动画">前往互動式WebGL 動畫</a> <img src="https://img.php.cn/upload/article/000/000/024/6e2b2d910982ba58209c1c53cd9c7f6d-2.gif" alt="一文詳解一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】"></p><p>這裡我建議你點擊<a href="https://divan.dev/demos/pingpong/" target="_blank">連結</a> 進入互動式WebGL 動畫動作一下. 你可以放慢或加速動畫,從不同的角度觀察。 </p><p>現在,我們新增三個玩家看看。 </p><pre class="brush:php;toolbar:false"> go player(table) go player(table) go player(table)
转到交互式 WebGL 动画 我们可以看到每个玩家都按照次序轮流操作,你可能会想为什么会这样。为什么多个玩家(goroutine)会按照严格的顺序接到“球”呢。
答案是 一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 运行时环境维护了一个 接收者 FIFO 队列 (存储需要从某一通道上接收数据的goroutine),在我们的例子里,每个玩家在刚发出球后就做好了接球准备。我们来看一下更复杂的情况,加入100个玩家。
for i := 0; i <p><a href="https://divan.dev/demos/pingpong100/" target="_blank">转到交互式 WebGL 动画</a> <img src="https://img.php.cn/upload/article/000/000/024/f022e6361217c7f4c90e16d8d0885f99-4.gif" alt="一文詳解一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 100"> </p><p>先进先出顺序很明显了,是吧? 我们可以创建一百万个goroutine,因为它们很轻量,但是对于实现我们的目的来说没有必要。我们来想想其他可以玩的。 例如, 常见的消息传递模式。</p><h3> <span class="header-link octicon octicon-link"></span><strong><span style="font-size: 18px;">Fan-In</span></strong> </h3><p>并发世界中流行的模式之一是所谓的 <em>fan-in</em> 模式。这与 <em>fan-out</em> 模式相反,稍后我们将介绍。简而言之,fan-in 是一项功能,可以从多个输入中读取数据并将其全部多路复用到单个通道中。</p><p>举例来说:</p><pre class="brush:php;toolbar:false">package main import ( "fmt" "time" ) func producer(ch chan int, d time.Duration) { var i int for { ch <p><a href="https://divan.dev/demos/fanin/" target="_blank">一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 to interactive WebGL animation</a> <img src="https://img.php.cn/upload/article/000/000/024/f022e6361217c7f4c90e16d8d0885f99-5.gif" alt="Fan-In Pattern"></p><p>如我们所见,第一个 <em>producer</em> 每100毫秒生成一次值,第二个每250毫秒生成一次值,但是 <em>reader</em> 会立即从这两个生产者那里接受值。实际上,多路复用发生在 <em>main</em> 的range循环中。</p><h3> <span class="header-link octicon octicon-link"></span><strong><span style="font-size: 18px;">Workers</span></strong> </h3><p>与 <em>fan-in</em> 相反的模式是 <em>fan-out</em> 或者<em>worker</em> 模式。多个 goroutine 可以从单个通道读取,从而在CPU内核之间分配大量的工作量,因此是 <em>worker</em> 的名称。在一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中,此模式易于实现-只需以通道为参数启动多个goroutine,然后将值发送至该通道-一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】运行时会自动地进行分配和复用 :)</p><pre class="brush:php;toolbar:false">package main import ( "fmt" "sync" "time" ) func worker(tasksCh <p><img src="https://img.php.cn/upload/article/000/000/024/bc82f8929dc05185db5d2b4542d2ca73-6.gif" alt="一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】"></p><p>这里值得一提的是:并行性。如您所见,所有goroutine并行’运行‘,等待通道给予它们’工作‘。鉴于上面的动画,很容易发现goroutine几乎立即接连地收到它们的工作。不幸的是,该动画在goroutine确实在处理工作还是仅仅是在等待输入的地方没有用颜色显示出来,但是此动画是在GOMAXPROCS=4的情况下录制的,因此只有4个goroutine有效地并行运行。我们将很快讨论这个主题。</p><p>现在,让我们做一些更复杂的事情,并启动一些有自己workers(subworkers)的workers。</p><pre class="brush:php;toolbar:false">package main import ( "fmt" "sync" "time" ) const ( WORKERS = 5 SUBWORKERS = 3 TASKS = 20 SUBTASKS = 10 ) func subworker(subtasks chan int) { for { task, ok := <p><a href="https://divan.dev/demos/workers2/" target="_blank">一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 to interactive WebGL animation</a> <img src="https://img.php.cn/upload/article/000/000/024/bc82f8929dc05185db5d2b4542d2ca73-7.gif" alt="Workers of workers"> 很好。当然,我们可以将worker和subworker的数量设置为更高的值,但是我试图使动画清晰易懂。</p><p>更酷的 fan-out 模式确实存在,例如动态数量的worker/subworker,通过通道发送通道,但是 fan-out 的想法现在应该很清楚了。</p><h3> <span class="header-link octicon octicon-link"></span><strong><span style="font-size: 18px;">服务器</span></strong> </h3><p>下一个常见的模式类似于扇出,但是会在很短的时间内生成goroutine,只是为了完成某些任务。它通常用于实现服务器-创建侦听器,循环运行accept()并为每个接受的连接启动goroutine。它非常具有表现力,可以实现尽可能简单的服务器处理程序。看一个简单的例子:</p><pre class="brush:php;toolbar:false">package main import "net" func handler(c net.Conn) { c.Write([]byte("ok")) c.Close() } func main() { l, err := net.Listen("tcp", ":5000") if err != nil { panic(err) } for { c, err := l.Accept() if err != nil { continue } go handler(c) } }
一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 to 交互式WebGL动画
这不是很有趣-似乎并发方面没有发生任何事情。当然,在引擎盖下有很多复杂性,这是我们特意隐藏的。 “简单性很复杂”.
但是,让我们回到并发性并向我们的服务器添加一些交互。假设每个处理程序都希望异步写入记录器。在我们的示例中,记录器本身是一个单独的goroutine
,它可以完成此任务。
package main import ( "fmt" "net" "time" ) func handler(c net.Conn, ch chan string) { ch <p><a href="https://divan.dev/demos/servers2/" target="_blank">一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 to 交互式WebGL动画</a> <img src="https://img.php.cn/upload/article/000/000/024/dfdd69afd5511ae20942d0af033a1db2-9.gif" alt="一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 2"> </p><p>不是吗?但是很容易看到,如果请求数量增加并且日志记录操作花费一些时间(例如,准备和编码数据),我们的* logger * goroutine很快就会成为瓶颈。我们可以使用一个已知的扇出模式。我们开始做吧。</p><h3> <span class="header-link octicon octicon-link"></span><strong><span style="font-size: 18px;">服务器+工作者</span></strong> </h3><p>带工作程序的服务器示例是记录器的高级版本。它不仅可以完成一些工作,而且还可以通过* results *通道将其工作结果发送回池中。没什么大不了的,但是它将我们的记录器示例扩展到了更实际的示例。</p><p>让我们看一下代码和动画:</p><pre class="brush:php;toolbar:false">package main import ( "net" "time" ) func handler(c net.Conn, ch chan string) { addr := c.RemoteAddr().String() ch <p><a href="https://divan.dev/demos/servers3/" target="_blank">一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 to 交互式WebGL动画</a> <img src="https://img.php.cn/upload/article/000/000/024/d04fb172e580bf3c4b0bca0edac0a234-10.gif" alt="Server + Worker"> 我们在4个goroutine之间分配了工作,有效地提高了记录器的吞吐量,但是从此动画中,我们可以看到记录器仍然可能是问题的根源。成千上万的连接在分配之前会汇聚在一个通道中,这可能导致记录器再次成为瓶颈。但是,当然,它会在更高的负载下发生。</p><h3> <span class="header-link octicon octicon-link"></span><strong><span style="font-size: 18px;">并发素筛(素筛指素数筛法)</span></strong> </h3><p>足够的扇入/扇出乐趣。让我们看看更复杂的并发算法。我最喜欢的例子之一是Concurrent Prime Sieve,可以在[<a href="https://talks.golang.org/2012/concurrency.slide" target="_blank">一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 Concurrency Patterns</a>]对话中找到。素数筛,或<a href="https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" target="_blank">[Eratosthenes筛)</a>是一种古老的算法,用于查找达到给定限制的素数。它通过按顺序消除所有质数的倍数来工作。天真的算法并不是真正有效的算法,尤其是在多核计算机上。</p><p>该算法的并发变体使用goroutine过滤数字-每个发现的素数一个goroutine,以及用于将数字从生成器发送到过滤器的通道。找到质数后,它将通过通道发送到* main *以进行输出。当然,该算法也不是很有效,特别是如果您想找到大质数并寻找最低的Big O复杂度,但是我发现它非常优雅。</p><pre class="brush:php;toolbar:false">// 并发的主筛 package main import "fmt" // 将序列2、3、4,...发送到频道“ ch”。 func Generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } //将值从通道“ in”复制到通道“ out”, //删除可被“素数”整除的那些。 func Filter(in <-chan int, out chan<- int, prime int) { for { i := <-in // Receive value from 'in'. if i%prime != 0 { out <- i // Send 'i' to 'out'. } } } //主筛:菊花链过滤器过程。 func main() { ch := make(chan int) // Create a new channel. go Generate(ch) // Launch Generate goroutine. for i := 0; i < 10; i++ { prime := <-ch fmt.Println(prime) ch1 := make(chan int) go Filter(ch, ch1, prime) ch = ch1 } }
,请以交互模式随意播放此动画。我喜欢它的说明性-它确实可以帮助您更好地理解该算法。 * generate * goroutine发出从2开始的每个整数,每个新的goroutine仅过滤特定的质数倍数-2、3、5、7 …,将第一个找到的质数发送给* main *。如果旋转它从顶部看,您会看到从goroutine发送到main的所有数字都是质数。漂亮的算法,尤其是在3D中。
现在,让我们回到我们的工作人员示例。还记得我告诉过它以GOMAXPROCS = 4运行吗?那是因为所有这些动画都不是艺术品,它们是真实程序的真实痕迹。
让我们回顾一下GOMAXPROCS是什么。
GOMAXPROCS设置可以同时执行的最大CPU数量。
当然,CPU是指逻辑CPU。我修改了一些示例,以使他们真正地工作(而不仅仅是睡觉)并使用实际的CPU时间。然后,我运行了代码,没有进行任何修改,只是设置了不同的GOMAXPROCS值。 Linux机顶盒有2个CPU,每个CPU具有12个内核,因此有24个内核。
因此,第一次运行演示了该程序在1个内核上运行,而第二次-使用了所有24个内核的功能。
WebGL动画-1| WebGL动画-24GOMAXPROCS1
這些動畫中的時間速度是不同的(我希望所有動畫都適合同一時間/ height),因此差異很明顯。當GOMAXPROCS = 1時,下一個工作人員只有在上一個工作完成後才能開始實際工作。在GOMAXPROCS = 24的情況下,加速非常大,而復用的開銷可以忽略不計。
不過,重要的是要了解,增加GOMAXPROCS並不總是可以提高效能,在某些情況下實際上會使它變得更糟。
#我們可以從一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發時間證明什麼呢?我想到的一件事情是goroutine洩漏。例如,如果您啟動goroutine,但超出範圍,可能會發生洩漏。或者,您只是忘記新增結束條件,而執行了for{}循環。
第一次在程式碼中遇到goroutine洩漏時,我的腦海中出現了可怕的圖像,並且在下個週末我寫了 expvarmon。現在,我可以使用WebGL來視覺化該恐怖圖像。
看一看:
只是看到此,我都會感到痛苦:) 所有這些行都浪費了資源,並且是您程式的定時炸彈。
我要說明的最後一件事是並行性與並發性之間的區別。這個主題涵蓋了 很多 ,Rob Pike在這個主題上做了一個精彩的演講。確實是#必須觀看的影片之一。
簡而言之,
並行是簡單的並行運行事物。
並發是一種建構程式的方法。
因此,並發程式可能是並行的,也可能不是並行的,這些概念在某種程度上是正交的。我們在演示 GOMAXPROCS 設定效果時已經看到了這一點。
我可以重複所有這些連結的文章和談話,但是一張圖片相當於說了一千個字。我在這裡能做的是可視化這個差異。因此,這是並行。許多事情並行運行。
這也是並行性:
#但這是並發的:
#還有這個:
這也是並發的:
為了創建這些動畫,我寫了兩個程式:gotracer 和gothree.js 函式庫。首先,gotracer執行以下操作:
產生的JSON範例:
接下來,gothree.js使用令人驚嘆的Three.js 函式庫的功能來使用WebGL繪製3D線和物件。我做了一些很小的包裝使其適合單一html頁面-就是這樣。
但是,這種方法非常有限制。我必須準確地選擇範例,重命名通道和goroutine,以使得或多或少複雜的程式碼產生正確的追蹤。使用這種方法,如果goroutine具有不同的名稱,就沒有簡單的方法來關聯goroutine之間的通道。更不用說透過chan類型的通道發送的通道。時序方面也存在很大的問題-輸出到stdout可能比發送值花費更多的時間,因此在某些情況下,我必須放置time.Sleep(一些毫秒)以獲取正確的動畫。
基本上,這就是為什麼我還沒有開源程式碼的原因。我正在玩 Dmitry Vyukov 的 執行跟踪器,它似乎提供了事件的詳細信息,但是沒有包含發送值得信息。也許有更好的方法可以實現預期的目標。如果您有想法,請在twitter給我寫信,或在評論中寫給我。如果能將這個為期兩週的工具擴展為任何一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】程式的真正的調試/追蹤工具,那將是非常棒的。
我也很樂意看到這裡未列出的更有趣的並發演算法和模式。歡迎隨時在評論中寫一個。
Happy coding!
UPD: 可在github.com/pan/gotrace 上使用此工具,並使用一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】 Execution Tracer和打了補丁的運行時生成跟踪。
另外,我願意接受新工作,因此,如果您公司/團隊對我感興趣,有難題需要使用一文詳解一文詳解一文詳解一文詳解Go中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】中的並發【20 張動圖示範】解決,我可以遠端工作(或您在巴塞隆納)並在招聘,請告訴我:)
英文原文網址:https://divan.dev/posts/go_concurrency_visualize/
更多程式相關知識,請造訪:程式設計視頻! !
以上是一文詳解Go中的並發【20 張動圖示範】的詳細內容。更多資訊請關注PHP中文網其他相關文章!