In the Go language, the correct use of concurrency

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

English Original: "Sane Concurrency with Go"

Glyph Lefkowitz recently wrote an introductory article in which he detailed some of the challenges of developing high-concurrency software, and if you develop software but do not read this article, then I suggest you read one. This is a very good article, modern software engineering should have a wealth of wisdom.

Extracted from multiple tidbits, but if I ventured to summarize the main points of view, the idea is that the combination of preemptive multitasking and general shared state leads to the complexity of the software development process that is not manageable, and developers may prefer to keep some of their sanity to avoid this kind of non-manageable complexity. Preemptive scheduling is good for real parallel tasks, but when mutable states are shared through multiple concurrent threads, explicit multitasking is more pleasing.

Mo Tian
Translated 6 months ago

1 Person top

top translation of good Oh!

Despite the collaborative multitasking, your code can still be complex, and it just has the opportunity to maintain a manageable amount of complexity. When control transfer is clear a code reader has at least some visible indications that things may be out of track. There is no clear mark that each new stage is a potential mine: "If this operation is not atomic, what happens at the end?" "Then the space between each command becomes an endless space black hole, and the awful heisenbugs appears

Over the past year, although work on Heka (a high-performance data, log and indicator processing engine) has mostly been developed using the go language. One of the highlights of go is that the language itself has some very useful concurrency primitives. But what about the concurrency of go, which needs to be observed by encouraging code footage that supports local reasoning.

Mo Tian
Translated 6 months ago

1 Person top

top translation of good Oh!

Not all the facts are good. All Goroutine access the same shared memory space, the state defaults to variable, but the Go Scheduler does not guarantee the accuracy of the context selection process. In a single-core setup, the run time of go goes to the "implicit collaborative work" category, and the list of asynchronous program models frequently mentioned in glyph is selected in 4. When GorOutine can run in parallel in multicore systems, the world is hard to get.

go can't protect you, but it doesn't mean you can't take steps to protect yourself. By using some of the primitives provided by go in the process of writing code, you can minimize the abnormal behavior generated by the associated preemption schedule. Take a look at the following glyph example of the Go interface in the "account Translation" code snippet (ignoring which floating-point numbers are not easy to end up storing fixed-point decimals)

    func transfer (amount float64, payer, payee *account,         server someservertype)  error {         if payer. Balance ()  < amount {             Return errors. New ("Insufficient funds")         }         log. Printf ("%s has sufficient funds",  payer)          Payee. Deposit (amount)         log. Printf ("%s received payment",  payee)         payer. Withdraw (amount)         log. Printf ("%s made payment",  payer)         server. Updatebalances (payer, pAyee)  // Assume this is magic and always works.         return nil    }

Mo Tian
Translated 6 months ago

0 Person Top

top translation of good Oh!

This is obviously not safe, if called from multiple goroutine, because they may get the same result from the deposit schedule concurrently, and then request more of the cancelled call's deposit variables together. It is best that the dangerous part of the code is not executed by multiple goroutine. This feature is implemented in this way:

    type transfer struct {         payer *account        payee *account         amount float64    }    var xferchan  = make (Chan *transfer)     var errchan = make (chan  Error)     func init ()  {        go  Transferloop ()     }    func transferloop ()  {         for xfer := range xferChan {             if xfer.payer.balance < xfer.amount  {                errchan  <- errors. New("Insufficient funds")                  continue            }             log. Printf ("%s has sufficient funds",  xfer.payer)              xfer.payee.deposit (Xfer.amount)              log. Printf ("%s received payment",  xfer.payee)              xfer.payer.withdraw (Xfer.amount)              log. Printf ("%s made payment",  xfer.payer)              errChan <- nil        }     }  &nbSp; func transfer (amount float64, payer, payee *account,         server someservertype)  error {         xfer := &transfer{             payer: payer,            payee:  payee,            amount: amount,         }        xferChan <-  xfer        err := <-errchan         if err == nil  {             server. Updatebalances (Payer, payee)  // still magic.        }        return err    } 

There's more code here, but we eliminate concurrency problems by implementing a trivial event loop. When the code is first executed, it activates a goroutine run loop. A forwarding request is passed into a newly created channel for this purpose. The result is returned to the outside of the loop via an erroneous channel. Because the channels are not buffered, they are locked, and through the transfer function regardless of how many concurrent forwarding requests are entered, they are continuously serviced through a single run event loop.

Mo Tian
Translated 6 months ago

0 Person Top

top translation of good Oh!

The code above looks a little awkward, maybe. A mutex (mutex) may be a better choice for such a simple scenario, but what I'm trying to prove is that you can apply an isolated state operation to a go routine. Even slightly awkward, it's good enough for most needs, and it works, even using the simplest account structure:

    type account struct {         balance float64    }    func  (A  *account)  balance ()  float64 {        return  a.balance    }    func  (a *account)  Deposit (amount  float64)  {        log. Printf ("depositing: %f",  amount)         a.balance +=  amount    }    func  (a *account)  withdraw (amount  float64)  {        log. Printf ("withdrawing: %f",  amount)         a.balance -=  amount    } 

But such a clumsy account implementation would seem naïve. It may be more effective to provide some protection for the account structure itself by not letting any recall operations greater than the current balance. What happens if we turn the recall function into something like this?:

Func (a *account) withdraw (amount float64) {If amount > a.balance {log. PRINTLN ("insufficient funds") return} log. Printf ("Withdrawing:%f", amount) a.balance-= amount}

Leoxu
Translated 6 months ago

0 Person Top

top translation of good Oh!

Unfortunately, this code suffers the same problem as our original Transfer implementation. Concurrent execution or unfortunate context switching means that we may end up with a negative balance. Fortunately, the idea of an internal event loop is also very good, even better, because the event loop Goroutine can be well coupled with each individual account structure instance. Here's an example to illustrate this point:

Type account struct {balance float64 Deltachan chan float64 Balancechan chan float64 Errchan Chan Error}

    func newaccount (Balance float64)   (a *account)  {         a = &Account{             balance:     balance,             deltachan:   make (Chan float64),             balancechan: make (Chan float64),             errchan:     make ( Chan error),        }         go a.run ()         return    }     func  (A *account)  balance ()  float64 {         return <-a.balancechan    }    func  (a *account)  Deposit (amount  float64)  error {        a.deltaChan <-  amount        return <-a.errchan    }     func  (A *account)  withdraw (Amount float64)  error {         a.deltaChan <- -amount         return <-a.errChan    }    func  (A  *account)  applydelta (Amount float64)  error {         newBalance := a.balance + amount         if newBalance < 0 {             return errors. New ("Insufficient funds")         }         a.balance = newBalance        return  nil    }    func  (A *account)  run ()  {         var delta float64         for {            select {             case delta = <-a.deltachan :                 a.errchan  <- a.applydelta (Delta)              case a.balancechan <- a.balance:                 // do nothing, we ' Ve accomplished our goal w/ the channel  put.            }         }    }

This API is slightly different, and the Deposit and withdraw methods now return errors. Instead of processing their requests directly, they put the adjustment of the account balance into Deltachan and access the Deltachan in the event loop when the Run method runs. Similarly, the Balance method continuously requests data in the event loop through blocking until it receives a value through Balancechan.

Zhao Liang-Blue Sky
Translated 6 months ago

0 Person Top

top translation of good Oh!

The key point to note is that the above code, all of the structure of internal data is worth direct access and modification is the event loop triggered by the *within* code to complete. If public API calls are performing well and interacting with the data using only the given channels, then regardless of the number of concurrent calls to public methods, we know that only one of them will be processed at any given time. Our time loop code is much easier to infer.

The core of the model is the design of Heke. When Heka starts, it reads the configuration file and launches each plug-in in its own go routine. With the clock signal, shutdown notification, and other control signals, the data is fed into the plug-in via the channel. This encourages plug-in authors to implement plug-in functionality using a schema of event loop types like the one described above.

Again, go won't protect you. It is entirely possible to write a Heka plug-in (or any architecture) that is loosely coupled with its internal data management and subject-matter conditions. But there are some small places to be aware of, and the free application of Go controversy detectors, which you can write code whose behavior can be predicted, even in the façade code of preemptive scheduling.

Leoxu
Translated 6 months ago

0 Person Top

top translation of good Oh!

All translations in this article are for learning and communication purposes only, please be sure to indicate the translator, source, and link to this article.
Our translation work in accordance with the CC agreement, if our work has violated your rights and interests, please contact us promptly
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.