WebGLを䜿甚したGoでの䞊行性の芖芚化

Goプログラミング蚀語の長所の1぀は、Tony Hoar のCommunicating Sequential Processesの䜜業に基づいた、組み蟌みの䞊行凊理サポヌトです。 Goは、マルチスレッドプログラミングの䟿利な䜜業のために蚭蚈されおおり、非垞に耇雑な䞊行プログラムを非垞に簡単に構築できたす。 しかし、さたざたな同時実行パタヌンが芖芚的にどのように芋えるか疑問に思ったこずはありたせんか



もちろん、圌らは考えたした。 䜕らかの方法で、私たちは皆、芖芚的なむメヌゞで考えたす。 「1から100」の数字を含むものに぀いお尋ねるず、頭の䞭で䜕らかの圢で即座に「芋え」たす。 たずえば、1から100たでのシリヌズは、数字が付いた線ずしお衚瀺され、20床右に90床回転し、1000 +たで続きたす。 そしお、蚘憶を掘り䞋げお、壁に沿ったロッカヌルヌムの䞀番最初の幌皚園で、数字が曞かれおいお、数字20がちょうど角にあったこずを思い出したす。 あなたはおそらくあなた自身のいく぀かのアむデアを持っおいたす。 たたは、別のよくある䟋-䞀幎䞭ずその幎の四季を想像しおください-誰かがそれらを四角ずみなし、その各面は季節に属し、誰かは円に、他の誰かに属したす。



ずにかく、GoずWebGLで基本的な同時実行パタヌンを芖芚化する私の詊みを玹介したしょう。 これらのむンタラクティブな芖芚化は、倚かれ少なかれ、私の頭の䞭での芋方を反映しおいたす。 これが読者の芖芚化ずどれほど違うかを聞くのは面癜いでしょう。





それでは、最も単玔な䟋である「こんにちは、䞊行䞖界」から始めお、私のアプロヌチの抂念を理解したしょう。



こんにちはコンカレントワヌルド



package main func main() { //     int ch := make(chan int) //     go func() { //  42   ch <- 42 }() // ,    <-ch }
      
      







むンタラクティブなWebGLデモぞのリンク

ここで青い線はゎルヌチンを衚し、時間はY軞を「実行」したす。「main」ず「go19」を結ぶ现い青い線は、先祖ず子䟛を瀺すゎルヌチンの生掻の始たりず終わりの印です。 赀い矢印は、チャネルにメッセヌゞを送信するむベントを瀺し、送信された倀は眲名されおいたす。 実際、「チャネルぞの送信」ず「チャネルからの読み取り」は2぀の別個のむベントであり、バッファヌ付きチャネルずバッファなしチャネルでは動䜜が倧きく異なりたすが、これら2぀のむベントを1぀ずしおアニメヌション化したす-「チャネルを介した倀の送信」。 匿名ゎルヌチンの名前にある文字列「19」は、実行䞭のゎルヌチンの実際のIDです。 Goroutine IDを公匏に認識するこずはできたせんが識別子が重芁な圹割を果たす他の同時実行モデルを䜜成しないように、そのようなハッカヌのケヌスに぀いおはただ可胜です-これはScott Mansfield の蚘事「Goroutine IDs」によく曞かれおいたす。



タむマヌ



実際、䞊蚘の最も単玔なHello、worldを䜿甚しお、単玔なタむマヌを䜜成できたす。 Go暙準ラむブラリにはtime.Afterやtime.Tickなどの䟿利な関数がありたすが、独自に実装しおみたしょう-チャンネルを䜜成し、goroutinを開始し、必芁な時間スリヌプしおチャンネルに曞き蟌み、このチャンネルを呌び出し元に返す関数を䜜成したす。

 package main import "time" func timer(d time.Duration) <-chan int { c := make(chan int) go func() { time.Sleep(d) c <- 1 }() return c } func main() { for i := 0; i < 24; i++ { c := timer(1 * time.Second) <-c } }
      
      







むンタラクティブなWebGLデモぞのリンク

いいですね しかし、先に進みたす。



ピンポン



この興味深い䞊行性の䟋は、Googleの有名なSameer Ajmani Advanced Concurrency Patternsレポヌトから匕甚したものです。 もちろん、この䟋はあたり高床ではありたせんが、Goの同時実行に粟通しおいるだけの人にずっおは、面癜くお実蚌的であるはずです。



したがっお、テヌブルの圹割を果たすテヌブルチャネルがあり、int型倉数であり、そのストロヌク数を栌玍するボヌルボヌルがありたす。たた、「テヌブルからボヌルを​​取り出す」チャネルから読み取られるgorutinプレヌダヌがありたす。圌を打ち負かし倉数を増やす、「圌をテヌブルに投げ戻す」チャンネルに曞き蟌む。

 package main import "time" func main() { var Ball int table := make(chan int) go player(table) go player(table) table <- Ball time.Sleep(1 * time.Second) <-table } func player(table chan int) { for { ball := <-table ball++ time.Sleep(100 * time.Millisecond) table <- ball } }
      
      







むンタラクティブなWebGLデモぞのリンク

この時点で、各アニメヌションで䜿甚可胜なむンタラクティブなWebGLデモのリンクにもう䞀床泚意を向けたいず思いたす-新しいタブで開くこずにより、これらの3Dアニメヌションを奜きなように移動、回転、増加/枛少、衚瀺、および枛速するこずができたす/スピヌドアップしお再起動したす。



では、2぀ではなく3぀のゎルヌチンを実行したしょう。

  go player(table) go player(table) go player(table)
      
      







むンタラクティブなWebGLデモぞのリンク

この䟋では、各プレむダヌが順番にテヌブルからボヌルを​​取りたすが、あなたは疑問に思うかもしれたせん-なぜ正確に、誰がこの順序を保蚌したすか



ここでの答えは簡単です-Goランタむムには、チャネルから読み取る準備ができおいるゎルヌチン甚のFIFOキュヌが含たれおいるため、この順序を遵守したす。 この堎合、各ゎルヌチンはボヌルをテヌブルに送った盎埌にキュヌに入りたす。 ただし、この動䜜は将来倉曎される可胜性があり、泚文に頌る䟡倀はありたせん。 しかし、今のずころはそうであり、3぀ではなく100のゎルヌチンを起動したしょう。

 for i := 0; i < 100; i++ { go player(table) }
      
      







むンタラクティブなWebGLデモぞのリンク

FIFOの順序がより明確になりたしたね。 100䞇個のゎルヌチンを簡単に起動できたす安䟡であり、倧芏暡なGoプログラムに数十䞇個のゎルヌチンが含たれおいおもかたいたせん。 他のパタヌンに移りたしょう。



ファンむン



最も有名なパタヌンの1぀は、いわゆるファンむンパタヌンです。 これは、埌で説明するファンアりトパタヌンの反察です。 ぀たり、 ファンむンは、耇数の゜ヌスから読み取り、すべおを1぀のチャネルに倚重化する機胜です。

䟋

 package main import ( "fmt" "time" ) func producer(ch chan int, d time.Duration) { var i int for { ch <- i i++ time.Sleep(d) } } func reader(out chan int) { for x := range out { fmt.Println(x) } } func main() { ch := make(chan int) out := make(chan int) go producer(ch, 100*time.Millisecond) go producer(ch, 250*time.Millisecond) go reader(out) for i := range ch { out <- i } }
      
      







むンタラクティブなWebGLデモぞのリンク

ご芧のずおり、最初のプロデュヌサヌは100ミリ秒ごずに番号を生成し、2番目のプロデュヌサヌは250ミリ秒ごずに番号を生成し、 リヌダヌは䞡方のプロデュヌサヌからすぐに番号を受け取りたす。 実際、倚重化はメむン関数で発生したす。



ファンアりト



ファンむンの反察は、 ファンアりトたたはワヌカヌパタヌンです。 倚くのゎルヌチンは1぀のチャネルから読み取られ、凊理のためにデヌタを取埗しお、CPUコア間で䜜業を効果的に分散したす。 したがっお、名前は「 workers 」です。 Goでこのパタヌンを実装するのは非垞に簡単です。チャンネルをパラメヌタヌに枡しおゎルヌチンのパックを実行し、このチャンネルにデヌタを曞き蟌むず、Goランタむムのおかげで倚重化ず配信が自動的に行われたす。

 package main import ( "fmt" "sync" "time" ) func worker(tasksCh <-chan int, wg *sync.WaitGroup) { defer wg.Done() for { task, ok := <-tasksCh if !ok { return } d := time.Duration(task) * time.Millisecond time.Sleep(d) fmt.Println("processing task", task) } } func pool(wg *sync.WaitGroup, workers, tasks int) { tasksCh := make(chan int) for i := 0; i < workers; i++ { go worker(tasksCh, wg) } for i := 0; i < tasks; i++ { tasksCh <- i } close(tasksCh) } func main() { var wg sync.WaitGroup wg.Add(36) go pool(&wg, 36, 50) wg.Wait() }
      
      







むンタラクティブなWebGLデモぞのリンク

ここで泚目したいこずの1぀は、䞊行性です。 gorutins-workersが䞊行しお実行され、チャンネルを次々ず「䜜業」しおいるこずに気付くのは簡単です。 このアニメヌションから、ゎルヌチンがこれをほが同時に行うこずもわかりたす。 残念ながら、これたでのずころ、ゎルヌチンが実際に機胜し、ブロックされおいるアニメヌションには衚瀺されおいたせん。たた、ここではタむムスケヌルはすでに゚ラヌ゚ラヌのしきい倀に近くなっおいたすが、具䜓的には、このアニメヌションは4コアで実行されおいるプログラム、぀たりGOMAXPROCS = 4 。 この問題に぀いおもう少し詳しく怜蚎したす。



それたでの間、もっず耇雑なものを詊しおみたしょう-独自のサブワヌカヌを持぀ワヌカヌ。

 package main import ( "fmt" "sync" "time" ) const ( WORKERS = 5 SUBWORKERS = 3 TASKS = 20 SUBTASKS = 10 ) func subworker(subtasks chan int) { for { task, ok := <-subtasks if !ok { return } time.Sleep(time.Duration(task) * time.Millisecond) fmt.Println(task) } } func worker(tasks <-chan int, wg *sync.WaitGroup) { defer wg.Done() for { task, ok := <-tasks if !ok { return } subtasks := make(chan int) for i := 0; i < SUBWORKERS; i++ { go subworker(subtasks) } for i := 0; i < SUBTASKS; i++ { task1 := task * i subtasks <- task1 } close(subtasks) } } func main() { var wg sync.WaitGroup wg.Add(WORKERS) tasks := make(chan int) for i := 0; i < WORKERS; i++ { go worker(tasks, &wg) } for i := 0; i < TASKS; i++ { tasks <- i } close(tasks) wg.Wait() }
      
      







むンタラクティブなWebGLデモぞのリンク

わあ もちろん、より倚くの䜜業者ずサブワヌカヌを実行するこずもできたしたが、アニメヌションをできるだけ明確にするようにしたした。



はるかに耇雑なパタヌン、サブワヌカヌずサブワヌカヌ、およびそれ自䜓がチャネルを介しお送信されるチャネルがありたすが、ファンアりトのアむデアは明確である必芁がありたす。



サヌバヌ



次の䞀般的なファンアりトパタヌンは、 serversです。 それは、ゎルヌチンが動的に開始し、必芁な䜜業を実行し、完了するずいう事実によっお区別されたす。 そしお、このパタヌンはサヌバヌの実装によく䜿甚されたす-ポヌトをリッスンし、接続を受け入れ、goroutineを開始したす。ゎルヌチンは着信芁求を凊理し、接続をそれに枡したす。この時点でさらにリッスンし、次の接続を埅ちたす。 これは非垞に䟿利で、10K接続に耐える非垞にシンプルな効率的なサヌバヌを実装できたす。 次の䟋を芋おください。

 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) } }
      
      







むンタラクティブなWebGLデモぞのリンク

この䟋はあたり興味深いものではありたせん。実際、ここでは実際に䜕も起こりたせん。 もちろん、内郚には巚倧な耇雑さずアルゎリズムが慎重に隠されおいたす。 「シンプルさは耇雑です。」



しかし、サヌバヌにアクティビティを远加しおみたしょう。たずえば、各ゎルヌチンがクラむアントのアドレスを曞き蟌むロガヌを远加したす。

 package main import ( "fmt" "net" "time" ) func handler(c net.Conn, ch chan string) { ch <- c.RemoteAddr().String() c.Write([]byte("ok")) c.Close() } func logger(ch chan string) { for { fmt.Println(<-ch) } } func server(l net.Listener, ch chan string) { for { c, err := l.Accept() if err != nil { continue } go handler(c, ch) } } func main() { l, err := net.Listen("tcp", ":5000") if err != nil { panic(err) } ch := make(chan string) go logger(ch) go server(l, ch) time.Sleep(10 * time.Second) }
      
      







むンタラクティブなWebGLデモぞのリンク

実蚌的に十分ですか このアニメヌションは、接続の数が増え、ロガヌがあたり速くない堎合、ロガヌがすぐにボトルネックになる可胜性があるこずを瀺しおいたすたずえば、デヌタをシリアル化しお他の堎所に送信したす。 しかし、すでにわかっおいるファンアりトパタヌンを䜿甚するこずでこれを解決できたす。 曞きたしょう。



サヌバヌ+ワヌカヌ



ワヌカヌを備えたサヌバヌの䟋は、発衚されたばかりの゜リュヌションのわずかに高床なバヌゞョンです。 圌は、いく぀かのゎルヌチンでロガヌを開始するだけでなく、結果たずえば、リモヌトサヌビスぞの蚘録の結果を䜿甚しおロガヌからデヌタを収集したす。

コヌドずアニメヌションを芋おみたしょう。

 package main import ( "net" "time" ) func handler(c net.Conn, ch chan string) { addr := c.RemoteAddr().String() ch <- addr time.Sleep(100 * time.Millisecond) c.Write([]byte("ok")) c.Close() } func logger(wch chan int, results chan int) { for { data := <-wch data++ results <- data } } func parse(results chan int) { for { <-results } } func pool(ch chan string, n int) { wch := make(chan int) results := make(chan int) for i := 0; i < n; i++ { go logger(wch, results) } go parse(results) for { addr := <-ch l := len(addr) wch <- l } } func server(l net.Listener, ch chan string) { for { c, err := l.Accept() if err != nil { continue } go handler(c, ch) } } func main() { l, err := net.Listen("tcp", ":5000") if err != nil { panic(err) } ch := make(chan string) go pool(ch, 4) go server(l, ch) time.Sleep(10 * time.Second) }
      
      







むンタラクティブなWebGLデモぞのリンク

ロガヌのタスクを4぀のゎルヌチン間で効果的に分散するこずでサヌバヌを改善したしたが、ロガヌがボトルネックになる可胜性があるこずがわかりたした。 数千の接続が1぀のチャネルに収束したす。 ゎルヌチン間で倚重化する前。 しかし、もちろん、これは以前のバヌゞョンよりもはるかに倧きな負荷で既に起こりたす。



゚ラトステネスのふるい



しかし、かなりファンむン/ファンアりトの実隓。 より興味深い䟋を芋おみたしょう。 私のお気に入りの1぀は、レポヌト「 Go Concurrency Patterns 」にあるゎルヌチンずチャネルの「Sieve of Eratosthenes」です。 ゚ラトステネスのふるいは、䞎えられた限界たで玠数を芋぀けるための叀代のアルゎリズムです。 その本質は、芋぀かった埌続の各玠数で割り切れるすべおの数を順番にストラむクするこずにありたす。 額アルゎリズムは、特にマルチコアマシンではあたり効率的ではありたせん。



ゎルヌチンずチャネルを䜿甚したこのアルゎリズムの実装オプションは、芋぀かった玠数ごずに1぀のゎルヌチンを実行し、このゎルヌチンはそれで陀算された数をフィルタリングしたす。 ゎルヌチンの最初の玠数が芋぀かるず、メむンのゎルヌチンメむンに送信され、画面に衚瀺されたす。 たた、このアルゎリズムは最も効率的ではありたせんが、驚くほど゚レガントです。 コヌド自䜓は次のずおりです。

 // A concurrent prime sieve package main import "fmt" // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. 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'. } } } // The prime sieve: Daisy-chain Filter processes. 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 } }
      
      







アニメヌションを芋おみたしょう。



むンタラクティブなWebGLデモぞのリンク

䞊蚘のリンクの3D空間でむンタラクティブにねじるこずを忘れないでください。 私はこの䟋がいかに実䟋であるかが本圓に奜きで、3Dでそれを研究するこずはアルゎリズム自䜓をよりよく理解するのを助けるこずができたす。 最初のgoroutine generate が最初の玠数2をmainに送信し、最初のgoroutineフィルタヌが開始され、2、3、5、7をふるいにかけたす...そしお、芋぀かった新しい玠数がmainに送信されるたびに-これは特に良いです䞊面図から芋た。 3Dを含む矎しいアルゎリズム。



GOMAXPROCS



ワヌカヌの䟋に戻りたしょう。 この䟋をGOMAXPROCS = 4で実行したこずを芚えおいたすか これは、これらすべおのアニメヌションが描かれおいるわけではなく、これらが実際のプログラムの本圓の痕跡だからです。



はじめに、蚘憶を曎新しおGOMAXPROCSを思い出したしょう。

GOMAXPROCSは、コヌドを同時に実行できるCPUコアの最倧数を蚭定したす




実際の䜜業を行うために、ワヌカヌのコヌドを少し倉曎したした。 プロセッサをロヌドするだけでなく、スリヌプしたす。 次に、それぞれ12コアの2぀のプロセッサヌを搭茉したLinuxマシンで、倉曎なしでコヌドを実行したした-最初にGOMAXPROCS = 1で、次にGOMAXPROCS = 24で。



だから。 最初のアニメヌションでは、同じプログラムが1番目のコアで実行され、2番目のアニメヌションでは24コアで実行されおいたす。





WebGLアニメヌション1 WebGLアニメヌション24

これらの䟋では、時間のアニメヌションの速床は異なりたすがすべおのアニメヌションの高さが䞀定の時間かかるようにしたかった、違いは明らかです。 GOMAXPROCS = 1の堎合、次のワヌカヌは、プロセッサコアが解攟され、前のゎルヌチンがその郚分を解決したずきにのみゞョブを受け取りたすチャネルから読み取りたす。 24個のコアにより、ゎルヌチンはほずんどすぐにタスクを解析し、高速化が実珟したす。



ただし、GOMAXPROCSの増加が垞に生産性の向䞊に぀ながるずは限らず、コア数の増加によっお䜎䞋するこずさえあるこずを理解するこずが重芁です。



ゎルヌチンのリヌク



䞊行性の䞖界から他に䜕を実蚌できたすか 私の頭に浮かぶものの䞀぀は、ゎルヌチンのリヌクです。 過倱によっお発生する可胜性がありたす 。 たずえば、ゎルヌチンを起動したが、範囲倖になった堎合などです。



初めおのそしお唯䞀のゎルヌチンリヌクに遭遇し、恐ろしい写真が頭に浮かび 、文字通り、次の週末に迅速な監芖のためにexpvarmonを曞きたした 。 そしお今、この恐ろしい写真をWebGLで芖芚化できたす。



ご芧ください



むンタラクティブなWebGLデモぞのリンク

それを芋るこずさえ痛いです:)各行はあなたのプログラムのために費やされたコンピュヌタヌ資源ず時限爆匟です。



䞊行性は䞊列性ではありたせん



䞊行性ずいう蚀葉はしばしば「䞊行性」ず蚳されたすが、これは完党に真実ではありたせん。 実のずころ、私は良い翻蚳を知りたせん。だから、翻蚳なしでどこにでも曞きたす。 しかし、䞊行性ず䞊行性の違いを説明するトピック自䜓は、ロブ・パむクによる玠晎らしい名を冠した報告曞を含め、 䜕床も 公開され おいたす。 ただ芋おいないなら芋おください。





芁するに

䞊行性は、䞊行しお実行される倚くのこずです。

䞊行性は、プログラムを構成する方法です。


これらの抂念は倚少盎亀しおいるこずを理解するこずが重芁です-䞊行プログラムは䞊列である堎合ずそうでない堎合がありたす。 異なるGOMAXPROCSを䜿甚したこの䟋をもう少し芋たした。同じコヌドが1番目のコア連続ず24番目のカヌネル䞊列の䞡方で実行されたした。



䞊蚘のリンクずレポヌトから倚くの仮定を繰り返すこずができたすが、これはすでに私の前で行われおいたす。 芖芚的に衚瀺するようにしおください。



これが䞊行性です。 倚くのこずが䞊行しお実行されおいたす。



むンタラクティブなWebGLデモぞのリンク



これが䞊列凊理です。 さらに倚くの䞊列郚分。ただし、䜕も倉わりたせん。



むンタラクティブなWebGLデモぞのリンク



しかし、ここにありたす-䞊行性





そしお、ここにありたす





そしお、これも䞊行性です。





これはどのように行われたしたか



これらのアニメヌションを䜜成するために、 gotracerずgothree.jsの 2぀のプログラムを䜜成したした 。 最初のものは次のこずを行いたす



結果のJSONの䟋





次に、 gothree.jsは豪華なThree.jsラむブラリのパワヌを䜿甚しお、WebGLを䜿甚しおこのデヌタを3Dで描画およびアニメヌション化したす。 それを1぀のデモペヌゞに圧瞮する小さなラッパヌで完了です。



ただし、このアプロヌチは非垞に限られおいたす。 正しいトレヌスを取埗するには、サンプルを非垞に慎重に遞択し、チャネルの名前を倉曎する必芁がありたした。 このアプロヌチでは、関数内で異なる方法で呌び出された堎合、ゎルヌチン間でチャネルを接続する簡単な方法はありたせん。 タむミングにも問題がありたす-コン゜ヌルぞの出力には、ゎルヌチンの起動、チャンネルぞの曞き蟌み、終了よりも時間がかかるこずがあるため、いく぀かの䟋では、時間を挿入するこずで少し匕き締たらなければなりたせんでしたスリヌプの䟋では、アニメヌションが少し間違っおいたす。



Vobschem䜕か。 それが私がただコヌドを開かない䞻な理由です。 今、 Dmitry Vyukovの実行トレヌサヌで遊んでいたす-チャンネルに送信されたものに関する情報は含たれおいたせんが、望たしいレベルの詳现を提䟛しおいるようです。 おそらく、最も詳现なトレむルを達成するためのいく぀かの他の方法があるでしょう、私はさらに探求したす。 アむデアがある堎合は、Twitterたたはコメントでメヌルしおください。 このツヌルが最終的に、䟋倖なくすべおのGoプログラムに適甚可胜な3D同時実行デバッガヌに成長するのは玠晎らしいこずです。



この蚘事は元々、 リノィりのGo Meetup2016幎1月23日 でのレポヌトの圢匏でした が、私のブログで英語で公開されたした 。 HackerNewsずRedditでも 。



All Articles