This is a creation in Article, where the information may have evolved or changed. The most useful feature of the go language is the
ConcurrencyAs the first supported language, using the goroutine, it is very easy to implement code
Concurrency, which makes go the important choice of network class application, this paper takes bank transfer as an example, expounds how to use the co-process to implement in the new version of Go 1.5
Concurrency。 This article also points out that all the processes between go 1.5 are only running in a single process, do not support multi-core CPU parallel computing, and 1.5 later to support multicore.
Golang Security and Concurrency
The following code is an implementation of a co-process:
Func Hello () { println ("hello!" ) }
// ---
Func Main () {
Testchan: = Make (Chan string)
Go Hello ()
Go func (Chan string) { println (<-testchan) } (Testchan) "World" }
|
The process is using the keyword "Go", which can be regarded as a standalone function or an anonymous function, which is non-clogging, because the process will be flexible scheduling. We also use channel channels, allowing the covariance to transfer variables to each other, like queue pipelines, which easily solves the problem of communication between the processes, and when something is sent to the channel, the channel blocks until the read operation occurs, so there is no risk of losing the message.
In previous versions of Go 1.5, all the coprocessors were running in a single process (like node. js) By default, which meant just
ConcurrencyBut not in parallel, because only one process is running at a time, and the internal scheduler dispatches them to ensure that all the threads are running.
The following code simulates a single-process approach:
int)
Finite_func: = func () { Testchan <-1 }
Infinite_func: = func () { for {} Testchan <-1 }
Go Finite_func () Go Infinite_func ()
println (<-testchan)
|
The first process returns the 1 value in the channel immediately, and the second infinite loop, because the single-threaded cause causes the program to hang until the two threads are completed, and if two processes are used, the program exits immediately when the first process returns results.
The above shows the basic knowledge of the process, below we look at the competition conditions, using simple online bank transfer case, each time a request will be sent from a account to transfer money to B account, the bank needs to transfer cash and output new account balance:
Type User struct { Cashint } Func (U *user) sendcash (to *user, amountint) BOOL { ifU.cash < Amount { returnFalse } / * Delay to demonstrate the race condition * / Time. Sleep (* time.millisecond) U.cash = U.cash-amount To. Cash = To. Cash + Amount return true }
Func Main () { Me: = user{cash:500} You: = user{cash:500}
http. Handlefunc ("/", func (w http. Responsewriter, R *http. Request) { Me.sendcash (&you, 50) Fmt. fprintf (W,"I have $%d\n", me. Cash) Fmt. fprintf (W,"You have $%d\n", you. Cash) Fmt. fprintf (W,"Total transferred: $%d\n", (you. Cash-500)) })
http. Listenandserve (": 8080", Nil) }
|
This is a generic go Web application that defines the user data structure, Sendcash is a service that transfers between two users, using the Net/http package, we create a simple HTTP server, and then route the request to a Sendcash method that transfers 50 dollars, Under normal operation, the code will run as we expected, each transfer of $50, once a user's account balance reached $0, you can no longer transfer money, because there is no money, but if we send a lot of requests quickly, the program will continue to transfer a lot of money, resulting in negative account balance.
This is a competition situation often talked about in textbooks race condition, in this code, the checking of the balance of the account is separated from the withdraw operation from the account, we imagine that if a request has just completed the account balance check, but has not yet taken the money, that is, did not reduce the account balance value While another request thread also checks the balance of the account, finding that the account balance has not been left at 0 (resulting in two requests taking the money together, resulting in a negative account balance), which is a typical "check-then-act" competition situation. This is a very common existence.
ConcurrencyBug
So how do we fix it? We certainly cannot remove the check operation, but to ensure that there is no other action between the check and the two actions of the money, the other language is to use the lock, when the account is updated, lock prohibit the simultaneous operation of other threads, ensure that there is only one process operation at a time, that is, repel lock mutex.
You can also use the go language to implement lock operations, as follows:
Type User struct { Cashint } varTransferlock *sync. Mutex Func (U *user) sendcash (to *user, amountint) BOOL { Transferlock.lock () / * Defer runs this function whenever Sendcash exits * / Defer Transferlock.unlock ()
if U.cash < amount { return False }
/ * Delay to demonstrate the race condition * / Time. Sleep (* time.millisecond)
U.cash = U.cash-amount To. Cash = To. Cash + Amount return true }
Func Main () { Transferlock = &sync. mutex{}
Me: = user{cash:500} You: = user{cash:500}
http. Handlefunc ("/", func (w http. Responsewriter, R *http. Request) { Me.sendcash (&you, 50) Fmt. fprintf (W,"I have $%d\n", me. Cash) Fmt. fprintf (W,"You have $%d\n", you. Cash) Fmt. fprintf (W,"Total transferred: $%d\n", (you. Cash-500)) })
http. Listenandserve (": 8080", Nil) }
|
But the problem of shrinking is obviously reduced.
ConcurrencyPerformance, is
ConcurrencyDesigned to be the biggest enemy, we recommend the use of channel channels in go, and we can use the event loop for more flexibility to implement the activity loop mechanism
Concurrency, we entrust a daemon to listen to the channel, when there is data in the channel, the transfer operation immediately, because the process is sequentially read the data in the channel, that is, cleverly avoid the competition situation, there is no need to use any state variables to prevent
ConcurrencyCompetition.
Type User struct { Cashint } Type Transfer struct { Sender *user Recipient *user Amountint } Func Sendcashhandler (Transferchan Chan Transfer) { varVal Transfer for{ val = <-transferchan Val. Sender.sendcash (Val. Recipient, Val. Amount) } } / * Sendcash is the same * /
Func Main () {
Me: = user{cash:500} You: = user{cash:500}
Transferchan: = Make (chan Transfer) Go Sendcashhandler (Transferchan)
http. Handlefunc ("/", func (w http. Responsewriter, R *http. Request) { Transfer: = Transfer{sender: &me, Recipient: &you, amount:50} Transferchan <-Transfer Fmt. fprintf (W,"I have $%d\n", me. Cash) Fmt. fprintf (W,"You have $%d\n", you. Cash) Fmt. fprintf (W,"Total transferred: $%d\n", (you. Cash-500)) })
http. Listenandserve (": 8080", Nil) }
|
The above code creates a more reliable system and avoids
ConcurrencyCompetition, but we will bring another security issue: DoS (Denial of service denial), if our transfer operation slows down, then the incoming request needs to wait for the transfer operation of the process to read the new data from the channel, but this thread is busy to take care of the transfer operation, There is no idle time to read the new data in the channel, this situation will cause the system to be vulnerable to Dos attacks, the outside world just send a large number of requests can let the system stop responding.
Some basic mechanisms such as the buffered channel can handle this situation, but the buffered channel has a memory limit and is not enough to hold all the requested data, and the optimization solution is to use the go outstanding "select" Statement:
http. Handlefunc ("/", func (w http. Responsewriter, R *http. Request) { Transfer: = Transfer{sender: &me, Recipient: &you, amount:50}
/ * Attempt the transfer * / Result: = Make (chan int)
Go func (Transferchan chan<-Transfer, Transfer Transfer, result chan<- int) { Transferchan <-Transfer Result <-1 } (Transferchan, transfer, result)
Select { Case <-result: Fmt. fprintf (W,"I have $%d\n", me. Cash) Fmt. fprintf (W,"You have $%d\n", you. Cash) Fmt. fprintf (W,"Total transferred: $%d\n", (you. Cash-500)) case <-time. After (time. Second * 10): Fmt. fprintf (W,"Your request had been received, but was processing slowly") } })
|
This raises the event loop, waits no more than 10 seconds, waits for more than timeout time, returns a message to the user telling them that the request has been accepted, may take some time to process, please wait patiently, using this method we reduce the Dos attack possible, a really robust can
ConcurrencyA system that handles transfers and does not use any locks is born.