Write a Function to Ask if You Want the Program to Run Again

The author selected the Multifariousness in Tech Fund to receive a donation every bit function of the Write for DOnations program.

Introduction

One of the popular features of the Get language is its first-form support for concurrency, or the ability of a program to practice multiple things at in one case. Being able to run code concurrently is becoming a larger function of programming as computers move from running a single stream of code faster to running more streams of code simultaneously. To run programs faster, a programmer needs to design their programs to run meantime, so that each concurrent function of the program can be run independently of the others. Two features in Go, goroutines and channels, make concurrency easier when used together. Goroutines solve the difficulty of setting up and running concurrent code in a program, and channels solve the difficulty of safely communicating between the code running concurrently.

In this tutorial, you will explore both goroutines and channels. First, yous will create a program that uses goroutines to run multiple functions at once. Then y'all will add together channels to that programme to communicate between the running goroutines. Finally, you'll add more goroutines to the program to simulate a program running with multiple worker goroutines.

Prerequisites

To follow this tutorial, you will need:

  • Go version 1.16 or greater installed, which you lot can practice past following our series, How to Install and Gear up a Local Programming Environment for Go.
  • Familiarity with Go functions, which you tin find in the How to Define and Call Functions in Go tutorial.

Running Functions at the Same Time with Goroutines

In a modernistic computer, the processor, or CPU, is designed to run as many streams of code as possible at the same time. These processors accept one or more "cores," each capable of running one stream of lawmaking at the same time. And so, the more than cores a program tin use simultaneously, the faster the program will run. However, in order for programs to take advantage of the speed increment that multiple cores provide, programs demand to be able to be split into multiple streams of lawmaking. Splitting a plan into parts tin can be one of the more challenging things to do in programming, merely Get was designed to make this easier.

One way Go does this is with a feature chosen goroutines. A goroutine is a special type of function that can run while other goroutines are also running. When a program is designed to run multiple streams of code at one time, the program is designed to run meantime. Typically, when a function is called, it will stop running completely before the code afterward it continues to run. This is known as running in the "foreground" because it prevents your programme from doing anything else before information technology finishes. With a goroutine, the part telephone call will continue running the side by side code right abroad while the goroutine runs in the "background". Code is considered running in the background when it doesn't prevent other code from running before information technology finishes.

The ability goroutines provide is that each goroutine can run on a processor cadre at the same fourth dimension. If your computer has 4 processor cores and your program has four goroutines, all iv goroutines can run simultaneously. When multiple streams of code are running at the same time on different cores like this, it's chosen running in parallel.

To visualize the difference betwixt concurrency and parallelism, consider the post-obit diagram. When a processor runs a function, it doesn't always run it from beginning to completion all at once. Sometimes the operating system will interleave other functions, goroutines, or other programs on a CPU core when a function is waiting for something else to happen, such as reading a file. The diagram shows how a program designed for concurrency can run on a single core as well every bit multiple cores. It also shows how more segments of a goroutine tin fit into the same timeframe (nine vertical segments, as seen in the diagram) when running in parallel than when running on a single cadre.

Diagram split into two columns, labeled Concurrency and Parallelism. The Concurrency column has a single tall rectangle, labeled CPU core, divided into stacked sections of varying colors signifying different functions. The Parallelism column has two similar tall rectangles, both labeled CPU core, with each stacked section signifying different functions, except it only shows goroutine1 running on the left core and goroutine2 running on the right core.

The left column in the diagram, labeled "Concurrency", shows how a plan designed effectually concurrency could run on a single CPU core by running function of goroutine1, then some other office, goroutine, or plan, then goroutine2, then goroutine1 again, and and then on. To a user, this would seem like the programme is running all the functions or goroutines at the same fourth dimension, fifty-fifty though they're actually being run in small parts one afterwards the other.

The column on the correct of the diagram, labeled "Parallelism", shows how that same program could run in parallel on a processor with two CPU cores. The first CPU core shows goroutine1 running interspersed with other functions, goroutines, or programs, while the second CPU core shows goroutine2 running with other functions or goroutines on that core. Sometimes both goroutine1 and goroutine2 are running at the same fourth dimension every bit each other, just on unlike CPU cores.

This diagram also shows another of Become'due south powerful traits, scalability. A programme is scalable when it tin can run on annihilation from a small computer with a few processor cores to a large server with dozens of cores, and take advantage of those boosted resources. The diagram shows that past using goroutines, your concurrent programme is capable of running on a single CPU core, only as more CPU cores are added, more than goroutines can exist run in parallel to speed upward the program.

To get started with your new concurrent program, create a multifunc directory in the location of your choosing. Y'all may already accept a directory for your projects, simply in this tutorial, you'll create a directory chosen projects. You tin create the projects directory either through an IDE or via the control line.

If yous're using the command line, begin past making the projects directory and navigating to it:

                      
  1. mkdir projects
  2. cd projects

From the projects directory, utilize the mkdir command to create the programme'south directory (multifunc) and then navigate into it:

                      
  1. mkdir multifunc
  2. cd multifunc

Once you're in the multifunc directory, open a file named main.become using nano, or your favorite editor:

                      
  1. nano main.become

Paste or type the post-obit code in the primary.get file to get started.

projects/multifunc/principal.get

                      bundle            main            import            (            "fmt"            )            func            generateNumbers            (total            int            )            {            for            idx            :=            i            ;            idx            <=            full;            idx++            {            fmt.            Printf            (            "Generating number %d\n"            ,            idx)            }            }            func            printNumbers            (            )            {            for            idx            :=            1            ;            idx            <=            3            ;            idx++            {            fmt.            Printf            (            "Printing number %d\n"            ,            idx)            }            }            func            main            (            )            {            printNumbers            (            )            generateNumbers            (            3            )            }                  

This initial plan defines two functions, generateNumbers and printNumbers, then runs those functions in the master part. The generateNumbers role takes the amount of numbers to "generate" as a parameter, in this case one through iii, and then prints each of those numbers to the screen. The printNumbers function doesn't take whatever parameters however, only it will also print out the numbers 1 through iii.

One time you've saved the primary.go file, run it using go run to run into the output:

                      
  1. become run principal.go

The output will wait similar to this:

                      

Output

Printing number 1 Press number 2 Printing number iii Generating number ane Generating number 2 Generating number 3

Y'all'll run across the functions run one after the other, with printNumbers running first and generateNumbers running 2nd.

At present, imagine that printNumbers and generateNumbers each takes 3 seconds to run. When running synchronously, or one subsequently the other like the last example, your program would take six seconds to run. First, printNumbers would run for 3 seconds, and then generateNumbers would run for three seconds. In your program, however, these two functions are independent of the other because they don't rely on information from the other to run. You tin can have advantage of this to speed up this hypothetical program by running the functions concurrently using goroutines. When both functions are running concurrently the programme could, in theory, run in half the time. If both the printNumbers and the generateNumbers functions take three seconds to run and both beginning at exactly the same time, the program could finish in 3 seconds. (The bodily speed could vary due to outside factors, though, such as how many cores the computer has or how many other programs are running on the reckoner at the aforementioned time.)

Running a function concurrently every bit a goroutine is similar to running a office synchronously. To run a function equally a goroutine (equally opposed to a standard synchronous part), you simply demand to add the go keyword before the function telephone call.

However, for the programme to run the goroutines concurrently, you'll need to brand one boosted change. You'll demand to add together a way for your program to wait until both goroutines have finished running. If you don't expect for your goroutines to terminate and your chief function completes, the goroutines could potentially never run, or only part of them could run and not consummate running.

To await for the functions to cease, you lot'll use a WaitGroup from Go's sync package. The sync package contains "synchronization primitives", such as WaitGroup, that are designed to synchronize various parts of a program. In your example, the synchronization keeps track of when both functions have finished running so you can exit the program.

The WaitGroup primitive works by counting how many things it needs to look for using the Add, Done, and Wait functions. The Add function increases the count by the number provided to the role, and Washed decreases the count by one. The Await function can then exist used to wait until the count reaches naught, significant that Washed has been called enough times to offset the calls to Add. Once the count reaches zero, the Await part will render and the programme will keep running.

Next, update the code in your main.go file to run both of your functions as goroutines using the go keyword, and add a sync.WaitGroup to the program:

projects/multifunc/principal.become

                      package            chief            import            (            "fmt"                          "sync"                        )            func            generateNumbers            (full            int                          ,              wg              *sync.WaitGroup            )            {                          defer              wg.              Washed              (              )                        for            idx            :=            1            ;            idx            <=            total;            idx++            {            fmt.            Printf            (            "Generating number %d\north"            ,            idx)            }            }            func            printNumbers            (            wg              *sync.WaitGroup            )            {                          defer              wg.              Done              (              )                        for            idx            :=            1            ;            idx            <=            3            ;            idx++            {            fmt.            Printf            (            "Printing number %d\n"            ,            idx)            }            }            func            main            (            )            {                          var              wg sync.WaitGroup            wg.              Add together              (              2              )                                      go                        printNumbers            (                          &wg            )                          go                        generateNumbers            (            3                          ,              &wg            )            fmt.              Println              (              "Waiting for goroutines to finish..."              )                        wg.              Await              (              )                        fmt.              Println              (              "Washed!"              )                        }                  

After declaring the WaitGroup, information technology will demand to know how many things to await for. Including a wg.Add(2) in the main role before starting the goroutines will tell wg to look for ii Washed calls earlier because the group finished. If this isn't washed before the goroutines are started, it'southward possible things will happen out of order or the code may panic because the wg doesn't know it should exist waiting for any Done calls.

Each role will then use defer to call Done to decrease the count by one after the office finishes running. The master office is also updated to include a telephone call to Look on the WaitGroup, so the main function will wait until both functions call Washed to keep running and leave the programme.

After saving your master.become file, run it using go run like yous did before:

                      
  1. go run main.get

The output will expect like to this:

                      

Output

Press number 1 Waiting for goroutines to stop... Generating number i Generating number 2 Generating number iii Printing number 2 Press number iii Done!

Your output may differ from what is printed here, and it's even probable to change every time you run the program. With both functions running concurrently, the output depends on how much fourth dimension Go and your operating system give for each function to run. Sometimes in that location is plenty time to run each function completely and you'll see both functions print their entire sequences uninterrupted. Other times, you'll run across the text interspersed like the output above.

An experiment you lot can try is removing the wg.Expect() phone call in the main function and running the program a few times with go run again. Depending on your computer, you may see some output from the generateNumbers and printNumbers functions, just it's also likely you won't see any output from them at all. When you lot remove the telephone call to Wait, the program will no longer look for both functions to finish running before it continues. Since the main role ends presently after the Expect function, there's a good chance that your program will reach the end of the master function and exit before the goroutines end running. When this happens, you'll encounter a few numbers printed out, only you won't see all 3 from each role.

In this department, you created a plan that uses the go keyword to run two goroutines concurrently and impress a sequence of numbers. You besides used a sync.WaitGroup to make your program wait for those goroutines to finish before exiting the program.

You may have noticed that the generateNumbers and printNumbers functions practice not accept render values. In Go, goroutines aren't able to return values similar a standard function would. You tin nevertheless use the go keyword to call a function that returns values, but those return values will be thrown out and you won't be able to access them. So, what do you exercise when you lot need information from one goroutine in another goroutine if you tin can't return values? The solution is to use a Go feature chosen "channels", which allow you to send data from ane goroutine to another.

Communicating Safely Between Goroutines with Channels

Ane of the more difficult parts of concurrent programming is communicating safely betwixt different parts of the plan that are running simultaneously. If you're not careful, y'all might see bug that are only possible with concurrent programs. For example, a data race can happen when 2 parts of a program are running concurrently, and ane function tries to update a variable while the other part is trying to read it at the same fourth dimension. When this happens, the reading or writing can happen out of order, leading to 1 or both parts of the program using the incorrect value. The proper noun "data race" comes from both parts of the program "racing" each other to admission the data.

While it'due south withal possible to run into concurrency issues like information races in Go, the language is designed to make it easier to avoid them. In addition to goroutines, channels are another characteristic that makes concurrency safer and easier to use. A channel can be thought of like a pipe between two or more unlike goroutines that data tin be sent through. One goroutine puts information into one end of the piping and some other goroutine gets that same data out. The difficult part of making sure the data gets from ane to the other safely is handled for y'all.

Creating a channel in Go is similar to how you would create a piece, using the built-in brand() function. The blazon declaration for a channel uses the chan keyword followed by the blazon of data y'all want to ship on the channel. For instance, to create a channel for sending int values, you would use the type chan int. If you wanted a channel for sending []byte vaules, it would be chan []byte, like so:

          bytesChan            :=            brand            (            chan            [            ]            byte            )                  

One time a channel is created, y'all tin send or receive information on the channel by using the arrow-looking <- operator. The position of the <- operator in relation to the channel variable determines whether you're reading from or writing to the channel.

To write to a channel, begin with the channel variable, followed by the <- operator, then the value you want to write to the aqueduct:

          intChan            :=            brand            (            chan            int            )            intChan            <-            10                  

To read a value from a channel, begin with the variable you desire to put the value into, either = or := to assign a value to the variable, followed by the <- operator, and and then the channel you lot want to read from:

          intChan            :=            make            (            chan            int            )            intVar            :=            <-            intChan                  

To proceed these ii operations straight, it can be helpful to remember that the <- arrow e'er points to the left (every bit opposed to ->), and the arrow points to where the value is going. In the case of writing to a channel, the arrow points the value to the channel. When reading from a aqueduct, the arrow points the channel to the variable.

Like a slice, a channel tin as well be read using the range keyword in a for loop. When a channel is read using the range keyword, each iteration of the loop will read the next value from the channel and put it into the loop variable. It will so continue reading from the channel until the aqueduct is closed or the for loop is exited in other ways, such as a break:

          intChan            :=            make            (            chan            int            )            for            num            :=            range            intChan            {            // Use the value of num received from the channel            if            num            <            1            {            interruption            }            }                  

In some cases, you may want only to allow a office to read from or write to a channel, but non both. To do this, you would add the <- operator onto the chan type declaration. Similar to reading and writing from a aqueduct, the channel type uses the <- arrow to permit variables to constrain a channel to only reading, only writing, or both reading and writing. For case, to ascertain a read-simply channel of int values, the blazon declaration would be <-chan int:

                      func            readChannel            (ch            <-            chan            int            )            {            // ch is read-only            }                  

If you lot wanted the channel to be write-merely, y'all would declare it equally chan<- int:

                      func            writeChannel            (ch            chan            <-            int            )            {            // ch is write-only            }                  

Find that the arrow is pointing out of the channel for reading, and pointing into the channel for writing. If the declaration doesn't take an arrow, as in the case of chan int, the channel tin can be used for both reading and writing.

Finally, in one case a channel is no longer being used it can be closed using the born close() role. This footstep is essential considering when channels are created and then left unused many times in a program, it can lead to what'south known as a retentiveness leak. A retentiveness leak is when a programme creates something that uses up memory on a figurer, but doesn't release that retentivity back to the computer once it'southward washed using it. This leads to the plan slowly (or sometimes not so slowly) using up more memory over fourth dimension, similar a water leak. When a channel is created with make(), some of the computer'due south memory is used upwardly for the channel, then when close() is called on the aqueduct, that retention is given dorsum to the computer to be used for something else.

Now, update the master.go file in your programme to use a chan int aqueduct to communicate between your goroutines. The generateNumbers function volition generate numbers and write them to the channel while the printNumbers function will read those numbers from the channel and print them to the screen. In the master function, you'll create a new channel to pass as a parameter to each of the other functions, then use close() on the channel to close it considering it will no longer exist used. The generateNumbers function should also not exist a goroutine any more than because in one case that part is washed running, the plan volition have finished generating all the numbers it needs to. This way, the close() function is simply called on the aqueduct before both functions have finished running.

projects/multifunc/main.go

                      package            main            import            (            "fmt"            "sync"            )            func            generateNumbers            (total            int            ,            ch              chan              <-              int              ,                        wg            *sync.WaitGroup)            {            defer            wg.            Washed            (            )            for            idx            :=            1            ;            idx            <=            total;            idx++            {            fmt.              Printf              (              "sending %d to channel\north"              ,              idx)                        ch              <-              idx            }            }            func            printNumbers            (            ch              <-              chan              int              ,                        wg            *sync.WaitGroup)            {            defer            wg.            Done            (            )                          for              num              :=              range              ch              {                        fmt.              Printf              (              "read %d from channel\n"              ,              num)                                      }                        }            func            main            (            )            {            var            wg sync.WaitGroup            numberChan              :=              make              (              chan              int              )                        wg.            Add            (            two            )            go            printNumbers            (            numberChan,                        &wg)                          generateNumbers              (              3              ,              numberChan,              &wg)                                      shut              (numberChan)                        fmt.            Println            (            "Waiting for goroutines to terminate..."            )            wg.            Await            (            )            fmt.            Println            (            "Washed!"            )            }                  

In the parameters for generateNumbers and printNumbers, you'll see that the chan types are using the read-merely and write-only types. Since generateNumbers only needs to be able to write numbers to the aqueduct, it'due south a write-only type with the <- pointer pointing into the channel. printNumbers only needs to exist able to read numbers from the channel, and so it'southward a read-only type with the <- arrow pointing away from the channel.

Even though these types could be a chan int, which would let both reading and writing, it can be helpful to constrain them to only what the role needs to avert accidentally causing your program to end running from something known as a deadlock. A deadlock tin can happen when ane part of a plan is waiting for another part of the program to exercise something, but that other part of the program is also waiting for the first part of the program to finish. Since both parts of the program are waiting on each other, the plan volition never continue running, well-nigh like when two gears seize.

The deadlock can happen due to the style aqueduct advice works in Become. When office of a program is writing to a channel, it will wait until another function of the plan reads from that channel earlier continuing on. Similarly, if a program is reading from a channel it volition wait until another part of the program writes to that channel before it continues. A part of a plan waiting on something else to happen is said to be blocking considering it'south blocked from continuing until something else happens. Channels block when they are being written to or read from. So if you have a function where y'all're expecting to write to a channel just accidentally read from the channel instead, your program may enter a deadlock because the channel volition never be written to. Ensuring this never happens is i reason to use a chan<- int or a <-chan int instead of just a chan int.

Ane other important attribute of the updated lawmaking is using shut() to shut the aqueduct one time it's done existence written to by generateNumbers. In this plan, close() causes the for ... range loop in printNumbers to exit. Since using range to read from a aqueduct continues until the channel it'due south reading from is airtight, if close isn't called on numberChan and so printNumbers will never finish. If printNumbers never finishes, the WaitGroup's Done method will never be called past the defer when printNumbers exits. If the Done method is never called from printNumbers, the plan itself will never get out considering the WaitGroup'south Expect method in the main function volition never continue. This is some other case of a deadlock because the main office is waiting on something that will never happen.

Now, run your updated code using the go run command on main.go again.

                      
  1. go run chief.go

Your output may vary slightly from what's shown beneath, only overall it should be similar:

                      

Output

sending i to channel sending 2 to aqueduct read 1 from aqueduct read 2 from aqueduct sending 3 to aqueduct Waiting for functions to finish... read three from channel Done!

The output from the plan shows that the generateNumbers part is generating the numbers 1 through three while writing them to the channel shared with printNumbers. One time printNumbers receives the number, it then prints it to the screen. After generateNumbers has generated all three numbers it will exit, allowing the main function to close the channel and await until printNumbers is finished. Once printNumbers finishes printing out the last number, it calls Done on the WaitGroup and the programme exits. Similar to previous outputs, the exact output you run across will depend on diverse exterior factors, such as when the operating system or Go runtime choose to run specific goroutines, but it should be relatively shut.

The do good of designing your programs using goroutines and channels is that once y'all've designed your program to be split up, you can calibration it up to more goroutines. Since generateNumbers is just writing to a aqueduct, it doesn't affair how many other things are reading from that channel. It will simply send numbers to anything that reads the channel. Y'all tin can take reward of this by running more than 1 printNumbers goroutine, so each of them will read from the same channel and handle the data concurrently.

Now that your program is using channels to communicate, open the chief.go file again and update your program then it starts multiple printNumbers goroutines. You will need to tweak the call to wg.Add together so information technology adds one for every goroutine you lot start. You don't need to worry most adding 1 to the WaitGroup for the call to generateNumbers any more because the plan won't continue without finishing the whole role, unlike when y'all were running it as a goroutine. To ensure it doesn't reduce the WaitGroup count when information technology finishes, you lot should remove the defer wg.Done() line from the function. Adjacent, adding the number of the goroutine to printNumbers makes information technology easier to see how the channel is read by each of them. Increasing the corporeality of numbers existence generated is also a good idea so that information technology's easier to see the numbers being spread out:

projects/multifunc/principal.become

                      ...                          func              generateNumbers              (total              int              ,              ch              chan              <-              int              ,              wg              *sync.WaitGroup)              {                                      for              idx              :=              1              ;              idx              <=              total;              idx++              {                        fmt.            Printf            (            "sending %d to channel\n"            ,            idx)            ch            <-            idx            }            }            func            printNumbers            (            idx              int              ,            ch            <-            chan            int            ,            wg            *sync.WaitGroup)            {            defer            wg.            Washed            (            )            for            num            :=            range            ch            {            fmt.            Printf            (            "%d:              read %d from aqueduct\n"                          ,              idx            ,            num)            }            }            func            principal            (            )            {            var            wg sync.WaitGroup 	numberChan            :=            make            (            chan            int            )                          for              idx              :=              ane              ;              idx              <=              three              ;              idx++              {                        wg.            Add            (                          ane                        )            go            printNumbers            (            idx,            numberChan,            &wg)            }            generateNumbers            (                          5                        ,            numberChan,            &wg)            close            (numberChan)            fmt.            Println            (            "Waiting for goroutines to finish..."            )            wg.            Wait            (            )            fmt.            Println            (            "Done!"            )            }                  

Once your main.go is updated, y'all tin can run your programme again using go run with chief.become. Your programme should start three printNumbers goroutines before continuing on to generating numbers. Your program should likewise now generate five numbers instead of three to brand it easier to see the numbers spread out amidst each of the three printNumbers goroutines:

                      
  1. become run main.go

The ouput may look similar to this (although your output might vary quite a flake):

                      

Output

sending i to channel sending 2 to channel sending 3 to aqueduct iii: read 2 from channel i: read 1 from channel sending 4 to channel sending v to channel three: read 4 from channel 1: read 5 from aqueduct Waiting for goroutines to end... 2: read three from aqueduct Done!

When you look at your plan output this fourth dimension, at that place's a good gamble it will vary greatly from the output yous see above. Since there are 3 printNumbers goroutines running, there's an element of hazard determining which one receives a specific number. When i printNumbers goroutine receives a number, it spends a small amount of time press that number to the screen, while another goroutine reads the next number from the channel and does the same affair. When a goroutine has finished its work of press the number and is ready to read some other number, it volition go back and read the channel again to print the adjacent i. If there are no more than numbers to be read from the channel, it will start to block until the next number can be read. Once generateNumbers has finished and close() is chosen on the channel, all three of the printNumbers goroutines will finish their range loops and exit. When all three goroutines have exited and called Washed on the WaitGroup, the WaitGroup's count will reach zero and the program will exit. Yous can also experiment with increasing or decreasing the amount of goroutines or numbers being generated to see how that affects the output.

When using goroutines, avoid starting too many. In theory, a program could have hundreds or even thousands of goroutines. However, depending on the estimator the program is running on, it could really be slower to have a college number of goroutines. With a high number of goroutines, there's a chance it could meet resources starvation. Every fourth dimension Go runs part of a goroutine, it requires a little bit of actress fourth dimension to first running again, in addition to the fourth dimension needed to run the lawmaking in the next function. Due to the actress time information technology takes, information technology's possible for the calculator to take longer to switch between running each goroutine than to really run the goroutine itself. When this happens, it'southward called resource starvation considering the plan and its goroutines aren't getting the resources they need to run, or are getting very few. In these cases, it may be faster to lower the number of parts in the program running concurrently because it will lower the time it takes to switch between them, and requite more time to running the program itself. Remembering how many cores the program is running on can be a expert starting point for deciding how many goroutines you desire to apply.

Using a combination of goroutines and channels makes it possible to create very powerful programs capable of scaling from running on small desktop computers up to massive servers. As yous saw in this section, channels can exist used to communicate betwixt as few as a couple of goroutines to potentially thousands of goroutines with minimal changes. If you have this into consideration when writing your programs, you'll be able to take reward of the concurrency available in Go to provide your users a improve overall experience.

Conclusion

In this tutorial, you created a plan using the go keyword to start meantime-running goroutines that printed out numbers equally they run. Once that programme was running, you lot created a new channel of int values using make(chan int), then used the channel to generate numbers in i goroutine and ship them to another goroutine to be printed to the screen. Finally, y'all started multiple "press" goroutines at the aforementioned time as an instance of how channels and goroutines can exist used to speed up your programs on multi-core computers.

If y'all're interested in learning more about concurrency in Go, the Effective Go certificate created by the Go squad goes into much more detail. The Concurrency is not parallelism Become web log post is as well an interesting follow-upward about the relationship between concurrency and parallelism, 2 terms that are sometimes mistakenly conflated to hateful the aforementioned matter.

This tutorial is also part of the DigitalOcean How to Code in Go series. The series covers a number of Get topics, from installing Go for the starting time fourth dimension to how to use the linguistic communication itself.

agaralwastion.blogspot.com

Source: https://www.digitalocean.com/community/tutorials/how-to-run-multiple-functions-concurrently-in-go

0 Response to "Write a Function to Ask if You Want the Program to Run Again"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel