You may have heard of the Go language in some way. It is becoming more and more popular, and there are good reasons to prove it. Go is fast, simple, and has a strong community support. The most exciting thing about learning this language is its concurrency model. The concurrency primitives of Go make it easy and fun to create multithreaded concurrent programs. I will introduce the concurrency primitives of Go through illustrations, hoping to point out the relevant concepts to facilitate subsequent learning. This article is for beginners of Go language programming and students who are ready to start learning go concurrency primitives (goroutines and Channels). # # Single Threaded program vs. multithreaded programs You may have written some single-threaded procedures. A common programming pattern is to combine multiple functions to perform a specific task, and only the previous function prepares the data, and the latter is called. [Single Gopher] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/SINGLE-GOPHER.JPEG) First we will write the first example code in the above pattern, a description of the mining process. It consists of three functions, which are responsible for the mining, mining and mine-hunting tasks. In this example, we use a set of strings to represent ' rock ' (mine) and ' ore ' (ore), each function takes them as input, and returns a set of "processed" strings. For a single-threaded application, the program may be designed as follows:! [Ore mining single-threaded Program] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/ORE-MINING-SINGLE-THREADED-PROGRAM.JPEG) It has three main functions: *finder*, * Miner* and *smelter*. All functions of this version of the program are run in a single thread, one after the other, and this thread (named Gary Gopher) needs to handle all the work. "' Gofunc Main () {themine: = [5]string{" Rock "," ore "," ore "," rock "," ore "}foundore: = Finder (themine) Minedore: = miNER (Foundore) Smelter (Minedore)} "at the end of each function printing" ore "the result of processing, the following output:" ' from Finder: [Ore ore ore]from Miner: [Minedore Minedore minedore]from Smelter: [Smeltedore smeltedore Smeltedore] ' This programming style has the advantage of being easy to design, but what happens when you want to take advantage of multiple threads and perform separate functions from each other? This is where concurrent programming works. [Ore Mining concurrent Program] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/ORE-MINING-CONCURRENT-PROGRAM.JPEG) This design makes "mining" more efficient. Multiple threads (gophers) are now running independently so that Gary is no longer responsible for all the work. One of the gopher is responsible for prospecting, one for mining, and the other for mine, which may be done at the same time. In order to introduce this concurrency feature into our code, we need to create methods for running Gophers independently and how they communicate with each other (transferring ore). This requires the concurrent primitives of Go: goroutines and channels. # # Goroutinesgoroutines can be seen as a lightweight thread. Creating a goroutine is very simple, just put the *go* keyword in front of the function call statement. To illustrate how simple this is, we create two finder functions and call them with *go* to print them every time they find "ore"! [Go MyFunc ()] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/GO.JPEG) "Gofunc main () {themine: = [5]string{" Rock "," ore "," ore ", "Rock", "Ore"}go finder1 (themine) go fiNder2 (Themine) <-time. After (time. Second * 5)//you can ignore this to now} "the output of the program is as follows:" ' Finder 1 found ore! Finder 2 found ore! Finder 1 found ore! Finder 1 found ore! Finder 2 found ore! Finder 2 found ore! ' can see that two finder is running concurrently. Which first finds the ore is not in a definite order, and the order is not always the same when executing multiple programs. This is a great progress! Now we have an easy way to create multithreaded (multi-gopher) programs, but what happens when we need independent goroutines to communicate with each other? Welcome to the magical world of *channels*. # # Channels! [Communication] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/COMMUNICATION.JPEG) Channels allows the Go routines to communicate with each other. You can think of the channel as a conduit, goroutines can send messages to it, or you can receive messages from other go routines. [My first channel] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ Learning-go-s-concurrency-through-illustrations/channel.jpeg) ' Gomyfirstchannel: = Make (chan string) ' Goroutines can send messages to the channel or receive messages from them. This is done through the arrow operator (<-), which indicates the flow of data in the channel. [Arrow] (Https://raw.githubusercontent.com/studygolang/gctt-images/master/Learning-Go-s-Concurrency-ThrougH-illustrations/channel-arrow.jpeg) "Gomyfirstchannel <-" Hello "//sendmyvariable: = <-Myfirstchannel// Receive ' Now through the channel we can get the ore gopher to the mining gopher immediately, without waiting to find all the ore. [Ore channel] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/ORE-CHANNEL.JPEG) I rewrote the mine-digging process and rewrote the prospecting and mining functions into unnamed functions. If you've never seen a lambda function, you don't have to pay much attention to this part, just know that each function will be called through the *go* keyword and run in its own goroutine. It is important to note how Goroutine passes data through the channel ' Orechan '. Don't worry, I'll explain the unnamed function on the last side. "' Gofunc Main () {themine: = [5]string{" Ore1 "," Ore2 "," ore3 "}orechan: = Make (Chan string)//Findergo func (mine [5]string) {for _, Item: = Range Mine {Orechan <-item//send}} (themine)//Ore Breakergo func () {for i: = 0; i < 3; i++ {foundore: = <-orechan//receivefmt. Println ("miner:received" + Foundore + "from Finder")}} () <-time. After (time. Second * 5)///Again, ignore this to now} "" from the output below, you can see that Miner reads from ' Orechan ' three times each time it receives a piece of ore. "' Miner:received ore1 from finderminer:received oRe2 from Finderminer:received Ore3 is great, now we can send data between the Goroutines (Gophers) of the program. Before we start writing complex programs with channels, let's first understand some of its key features. # # # Channel Blockingchannels blocking goroutines occurs in various situations. This allows for a brief synchronization between each other before the Goroutines can run happily in their own way. # # Blocking on a send! [Blocking on send] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/BLOCKING-ON-SEND.JPEG) Once a goroutine (Gopher) sends data to a channel, It is blocked until another goroutine takes data from the channel. # # Blocking on a receive! [Blocking on receive] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ Learning-go-s-concurrency-through-illustrations/blocking-on-receive.jpeg) is similar to the sending situation where a goroutine may block the wait from a channel Gets the data if there are no other goroutine to send data to the channel. The concept of first-contact blocking can be confusing, but you can think of it as a trade between two goroutines (Gophers). One of the gopher, whether waiting for the money or the money, needs to wait for the other side of the deal to appear. Now that you understand the different scenarios in which Goroutine can block through channel communication, let's discuss two different types of channels: *unbuffered* and *buffered*. Choosing which channel to use may change the running performance of the program. # # unbuffered channels! [Unbuffered ChanneL] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/UNBUFFERED-CHANNEL.JPEG) In the previous example we have been using unbuffered channels, What makes them different is that only one piece of data can pass at a time. # # Buffered Channels! [Buffered Channel] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/BUFFERED-CHANNEL.JPEG) in concurrent programs, time coordination is not always perfect. In the case of mining, we may encounter situations where mining gopher the time it takes to process a piece of ore, and the ore-hunting gohper may have found 3 ore. We can use the *buffered* channel in order not to waste a lot of time waiting for mining gopher to send ore to the mining gopher. Let's start by creating a buffered channel with a capacity of 3. "' Gobufferedchan: = Make (Chan string, 3) ' buffered and unbuffered channels work similar, but a little different--before we need another gorountine to take the data, we can go to B Uffered Channel sends multiple copies of data. [Cap 3 buffered channel] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ Learning-go-s-concurrency-through-illustrations/cap-3-buffered-channel.jpeg) ' GobufferedChan: = Make (Chan string , 3) go func () {Bufferedchan <-"first" FMT. Println ("Sent 1st") Bufferedchan <-"Second" FMT. PrinTLN ("Sent 2nd") Bufferedchan <-"Third" FMT. Println ("Sent 3rd")} () <-time. After (time. Second * 1) go func () {firstread: = <-bufferedchanfmt.println ("Receiving ...") Fmt. Println (firstread) Secondread: = <-bufferedchanfmt.println (secondread) Thirdread: = <-bufferedchanfmt.println ( Thirdread)} () ' Two goroutines is printed in the following order: ' ' Sent 1stSent 2ndSent 3rdReceiving. Firstsecondthird "For the sake of simplicity, we do not use buffered channels in the final program. But it is important to know which channel to use. > Note: Using buffered channels does not prevent blocking from occurring. For example, if the prospecting gopher is 10 times times faster than the mining gopher and they communicate through a buffered channel with a capacity of 2, there will still be multiple blockages in the ore-hunting gopher. # # put it all together now with the power of goroutines and channels, we can use the concurrency primitives of Go to write a program that gives full play to the advantages of multithreading. [Putting it all together] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ Learning-go-s-concurrency-through-illustrations/all-together.jpeg) "Gothemine: = [5]string{" Rock "," ore "," ore "," Rock "," ore "}orechannel: = Make (Chan string) Minedorechan: = Make (Chan string)//Findergo func (mine [5]string) {for _, item : = Range Mine {if Item = = "Ore" {Orechannel <-Item//send item on Orechannel}}} (themine)//Ore Breakergo func () {for I: = 0, I < 3; i++ {foundore: = <-orechannel//read from orechannelfmt.println ("From Finder:", Foundore) Minedorechan <-"Minedore"//send to Minedorechan}} ()//Smeltergo func () {for i: = 0; i < 3 ; i++ {minedore: = <-minedorechan//read from Minedorechanfmt.println ("from Miner:", Minedore) fmt. Println ("From Smelter:ore is Smelted")}} () <-time. After (time. Second * 5)//Again, you can ignore the program output as follows: "' from Finder:orefrom finder:orefrom miner:minedorefrom smelter:or E is Smeltedfrom miner:minedorefrom smelter:ore are smeltedfrom finder:orefrom miner:minedorefrom Smelter:ore is smelt Ed ' has made a lot of improvements compared to the original example. Each function now runs independently in its own goroutines. In addition, each time a piece of ore is processed, it is brought into the next stage of the mining line. To focus on understanding the basic concepts of goroutines and channel, I did not mention some of the important information above, and if you do not know it, they can cause some trouble when you start programming. Now that you understand how goroutines and channel work, let's start with some other information that you should know before you begin writing code with them. # # Before you start, you should know ... # # # anonymous goroutines! [Anonymous GorOutine] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ Learning-go-s-concurrency-through-illustrations/anonymous-go-routine.jpeg) is similar to how you can use the *go* keyword to make a function run in its own goroutine, We can create an anonymous function in the following way and run it in its goroutine: "go//Anonymous Go routinego func () {fmt. Println ("I ' m running in my own Go Routine")} () "If you only need to call the function once, in this way we can let it run in its own goroutine without having to create a formal function declaration. # # # Main function is a goroutine! [Main Func] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/MAIN-FUNC.JPEG) The main function does run in its own goroutine! It is more important to know that once the main function returns, it will turn off other goroutines that are currently running. That's why we set up a timer at the end of the main function-it creates a channel and sends a value after 5 seconds. "' Go<-time. After (time. Second * 5)//receiving from Channel after 5 sec "remember how the Goroutine from the channel is blocked until the data is sent to it?" This will happen with main routine by adding this line of code. It will block to give the other Goroutines 5 seconds to run. There is now a better way to block the main function until all other goroutines have run out. The usual practice is to create a *done channel*, which is blocked while waiting to read it. Once the work is done and the data is sent to the channel, the program will end. [Done Chan] (Https://raw. githubusercontent.com/studygolang/gctt-images/master/learning-go-s-concurrency-through-illustrations/ DONE.JPEG) "Gofunc main () {Donechan: = Make (Chan string) go func () {//Does some Work...donechan <-" I ' m All Done! "} () <-donechan//block until go routine signals work was done} ' # # # You can traverse the channel in the previous example we let miner iterate 3 times in the For loop from Chann The data is read in the El. What if we don't know exactly how many pieces of ore will be received from the Finder? Well, like iterating over the collection data type (note: Slice, for example), you can also traverse a channel. To update the previous miner function, we can write: "' go//Ore breakergo func () {for foundore: = Range Orechan {fmt. Println ("miner:received" + Foundore + "from Finder")}} () "Because Miner needs to read all the data that the Finder sends to it, traversing the channel ensures that we receive all the data that has been sent." The > Traversal channel will block until new data is sent to the channel. The only way to avoid go routine blocking after all the data has been sent is to turn off the channel with "close". # # # to channel non-blocking read but have you just told us how the channel is blocking goroutine?! Yes, but there is also a technique to use Go's *select case* statement to achieve non-blocking reading of the channel. By using this statement, if the channel has data, the Goroutine will read from it, otherwise it will execute the default branch. "' Gomychan: = Make (Chan string) go func () {Mychan <-" message! "} () Select {case msg: = <-Mychan:fmt. PRINTLN (msg) default:fmt. Println ("No Msg")}<-time. After (time. Second * 1) Select {case msg: = <-mychan:fmt. PRINTLN (msg) default:fmt. Println ("No Msg")} ' program output is as follows: ' No msgmessage! ' # # # # # # # # # # # # # # # # # # # # # # # # for non-blocking write non-blocking writes of channel is also implemented using the same *select case* statement, the only difference is that case The statement looks like it was sent rather than received. ' Goselect {case Mychan <-' message ': FMT. PRINTLN ("sent the message") default:fmt. PRINTLN ("no Message Sent")} ' # # WHERE to learn next! [Where go] (https://raw.githubusercontent.com/studygolang/gctt-images/master/ LEARNING-GO-S-CONCURRENCY-THROUGH-ILLUSTRATIONS/WHERE-GO.JPEG) has many lectures and blogs that describe channels and goroutines in more detail. Now that you have a deep understanding of the purpose and application of these tools, you should be able to make the most of the following articles and speeches. > [*google I/O 2012-go Concurrency patterns*] (https://www.youtube.com/watch?v=f6kdp27TYZs&t=938s) >> [* Rob pike-' Concurrency is not Parallelism ' *] (HTTPS://WWW.YOUTUBE.COM/WATCH?V=CN_DPYBZKSO) >> [*gophercon 2017: Edward muller-go anti-patterns*] (https://www.youtube.com/watch?v=ltqV6pDKZD8&t=1315s) Thank you for taking the time to read this article. I want you to understand the basic concepts of goroutines and channels, and to use themTo the benefits of writing concurrent programs.
via:https://medium.com/@trevor4e/learning-gos-concurrency-through-illustrations-8c4aff603b3
Author: Trevor Forrey Translator: mbyd916 proofreading: polaris1119
This article by GCTT original compilation, go language Chinese network honor launches
This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove
1251 reads ∙1 likes