2 programming schemes for the Go language concurrency model _golang

Source: Internet
Author: User
Tags mutex

Overview

I've been looking for a good way to explain the concurrency model of the Go language:

Do not communicate through shared memory, instead, you should share memory through communication

But I didn't find a good explanation to meet my requirements below:

1. An example to illustrate the initial problem
2. Provide a shared memory solution
3. Provide a communication solution

I will explain this article from these three aspects.

After reading this article you should understand the model of sharing memory through communication and the difference between it and communicating through shared memory, and you will see how to solve the problem of accessing and modifying shared resources separately through these two models.

Premise

Imagine we're going to visit a bank account:

Copy Code code as follows:

Type Account Interface {
Withdraw (UINT)
Deposit (UINT)
Balance () int
}

Type Bank struct {
Account Account
}

Func Newbank (account account) *bank {
Return &bank{account:account}
}

Func (bank *bank) Withdraw (amount uint, actor_name string) {
Fmt. Println ("[-]", Amount, Actor_name)
Bank.account.Withdraw (amount)
}

Func (bank *bank) deposit (amount uint, actor_name string) {
Fmt. Println ("[+]", amount, Actor_name)
Bank.account.Deposit (amount)
}

Func (bank *bank) Balance () int {
Return Bank.account.Balance ()
}

Because account is an interface, we provide a simple implementation:

Copy Code code as follows:

type Simpleaccount struct{
  balance int
}

Func Newsimpleaccount (balance int) *simpleaccount {
  return &simpleaccount{balance:balance}
}

Func (ACC *simpleaccount) Deposit (amount uint) {
  acc.setbalance (acc.balance + int (amount))
}

Func (ACC *simpleaccount) WITHD Raw (amount uint) {
  if acc.balance >= Int (mount) {
    acc.setbalance acc.balance-int (AM ount)
 } else {
    panic ("Jack dies")
 }
}

Func (ACC *simpleaccount) Bala NCE () int {
  return acc.balance
}

Func (ACC *simpleaccount) setbalance (balance int) {
  AC C.add_some_latency ()  //Add a delay function to facilitate demonstration of
  acc.balance = Balance
}

Func (ACC *simpleaccount) add_s Ome_latency () {
  <-time. After (time). Duration (Rand. INTN) * time.millisecond)
}

You may notice that the balance has not been directly modified, but is being put into the Setbalance method for modification. This is designed to better describe the problem. I'll make an explanation later.

Once we have all the above parts, we can use it as follows:

Copy Code code as follows:

Func Main () {
Balance: = 80
B: = Newbank (Bank. Newsimpleaccount (balance))

Fmt. PRINTLN ("Initialization balance", b.balance ())

B.withdraw (30, "Mai Yili")

Fmt. Println ("-----------------")
Fmt. Println ("Remaining balance", b.balance ())
}

Running the above code will output:

Copy Code code as follows:

Initialize the balance of 80
[-] 30 Ma Yili
-----------------
Remaining Balance 50

That's right!

It's good. In real life, a bank account can have a lot of ancillary cards, different ancillary cards can be the same accounts for access to money, so we have to modify the code:

Copy Code code as follows:

Func Main () {
Balance: = 80
B: = Newbank (Bank. Newsimpleaccount (balance))

Fmt. PRINTLN ("Initialization balance", b.balance ())

Done: = Make (chan bool)

Go func () {B.withdraw (30, "Mai Yili"); Done <-true} ()
Go func () {B.withdraw (10, ' Liade '); done <-true} ()

Wait for goroutine execution to complete
<-done
<-done

Fmt. Println ("-----------------")
Fmt. Println ("Remaining balance", b.balance ())
}

Here are two supplementary cards to take money from the account number, to see the output results:

Copy Code code as follows:

Initialize the balance of 80
[-] 30 Ma Yili
[-] 10 Liade
-----------------
Remaining Balance 70

This article is happy bad:

The result is, of course, wrong, and the remaining balance should be 40 instead of 70, so let's see what's wrong.

Problem

An invalid state can occur when concurrent access to a shared resource is possible.

In our example, when two affiliate cards are taken from the same account at the same time, we end up with the remaining balance (that is, the invalid state) of the error in the bank accounts (that is, shared resources).

Let's take a look at the execution time:

Copy Code code as follows:

Handling situation
--------------
_ Ma Yili _|_ Liade _
1. Get Balance 80 | 80
2. Withdraw Money-30 | -10
3. Current remaining 50 | 70
... | ...
4. Set Balance 50? 70//should be set first which is better?
5. After the establishment of the effective
--------------
6. Remaining Balance 70

Above... Describes our add_some_latency implementation of the delay situation, the real world often occur delays. So the last remaining balance is determined by the supplementary card that sets the balance at the end.

Solutions

We solve this problem in two ways:

1. Shared Memory Solution
2. Communication Solutions

All solutions are simply encapsulated simpleaccount to implement the protection mechanism.

Solutions for Shared memory

Also called "Communication through shared memory".

This scenario implies the use of locking mechanisms to prevent simultaneous access and modification of shared resources. The lock tells the other handler that the resource is already occupied by a handler, so the other handlers need to queue up until the current handler has finished processing the program.

Let's see how the Lockingaccount is implemented:

Copy Code code as follows:

Type Lockingaccount struct {
Lock sync. Mutex
Account *simpleaccount
}

Encapsulate the Simpleaccount
Func newlockingaccount (balance int) *lockingaccount {
return &lockingaccount{account:newsimpleaccount (Balance)}
}

Func (ACC *lockingaccount) deposit (amount uint) {
Acc.lock.Lock ()
Defer Acc.lock.Unlock ()
Acc.account.Deposit (amount)
}

Func (ACC *lockingaccount) withdraw (amount uint) {
Acc.lock.Lock ()
Defer Acc.lock.Unlock ()
Acc.account.Withdraw (amount)
}

Func (ACC *lockingaccount) Balance () int {
Acc.lock.Lock ()
Defer Acc.lock.Unlock ()
Return Acc.account.Balance ()
}

Direct and Clear! Note Lock sync. Lock,lock. Lock (), lock. Unlock ().

This way, each time an affiliate card accesses the bank account (that is, the shared resource), the attached card automatically gets the lock until the final operation is complete.

Our lockingaccount are used as follows:

Copy Code code as follows:

Func Main () {
Balance: = 80
B: = Newbank (Bank. Newlockingaccount (balance))

Fmt. PRINTLN ("Initialization balance", b.balance ())

Done: = Make (chan bool)

Go func () {B.withdraw (30, "Mai Yili"); Done <-true} ()
Go func () {B.withdraw (10, ' Liade '); done <-true} ()

Wait for goroutine execution to complete
<-done
<-done

Fmt. Println ("-----------------")
Fmt. Println ("Remaining balance", b.balance ())
}

The result of the output is:

Copy Code code as follows:

Initialize the balance of 80
[-] 30 Ma Yili
[-] 10 Liade
-----------------
Remaining balance 40

Now the results are correct!

In this example, when the first handler is locked and the shared resource is exclusive, the other handler can only wait for it to execute.

Let's take a look at the execution, assuming Ma Yili got the lock first:

Copy Code code as follows:

Processing process
________________
_ Ma yili _|__ Liade __
Lock ><
Get Balance 80 |
Fetch Money-30 |
Current Balance 50 |
... |
Set Balance 50 |
Unlock <>
|
Current Balance 50
|
Lock ><
Get Balance | 50
Take Money | -10
Current Balance | 40
| ...
Set Balance | 40
Unlock <>
________________
Remaining balance 40

Our handlers now have the right results in the process of accessing shared resources.

Solutions through communication

Also called "Sharing memory by communication".

The account is now named Concurrentaccount and implemented as follows:

Copy Code code as follows:

Type Concurrentaccount struct {
Account *simpleaccount
Deposits Chan UINT
Withdrawals Chan UINT
Balances Chan Chan int
}

Func newconcurrentaccount (amount int) *concurrentaccount{
ACC: = &concurrentaccount{
Account: &simpleaccount{balance:amount},
Deposits:make (chan uint),
Withdrawals:make (chan uint),
Balances:make (Chan Chan int),
}
Acc.listen ()

Return ACC
}

Func (ACC *concurrentaccount) Balance () int {
CH: = make (chan int)
Acc.balances <-Ch
Return <-ch
}

Func (ACC *concurrentaccount) deposit (amount uint) {
Acc.deposits <-Amount
}

Func (ACC *concurrentaccount) withdraw (amount uint) {
Acc.withdrawals <-Amount
}

Func (ACC *concurrentaccount) listen () {
Go func () {
for {
Select {
Case AMNT: = <-acc.deposits:
Acc.account.Deposit (AMNT)
Case AMNT: = <-acc.withdrawals:
Acc.account.Withdraw (AMNT)
Case CH: = <-acc.balances:
CH <-acc.account.Balance ()
}
}
}()
}

Concurrentaccount also encapsulates the simpleaccount, and then adds a communication channel

Call code and lock version of the same, here is not written, the only difference is the initialization of the bank account:

Copy Code code as follows:

B: = Newbank (Bank. Newconcurrentaccount (balance))

The result of the run is the same as the lock version:

Copy Code code as follows:

Initialize the balance of 80
[-] 30 Ma Yili
[-] 10 Liade
-----------------
Remaining balance 40

Let's take a closer look at the details.

How does sharing memory through communication work

Some basic points of attention:

The shared resource is encapsulated in a control flow.

The result is that resources become unshared. No handlers can access or modify resources directly. You can see that the methods of accessing and modifying resources do not actually change anything.

Copy Code code as follows:

Func (ACC *concurrentaccount) Balance () int {
CH: = make (chan int)
Acc.balances <-Ch
Balance: = <-ch
Return balance
}
Func (ACC *concurrentaccount) deposit (amount uint) {
Acc.deposits <-Amount
}

Func (ACC *concurrentaccount) withdraw (amount uint) {
Acc.withdrawals <-Amount
}

Access and modification are communicated through the message and control process.

Any movement of access and modification in the control process occurs sequentially.

When the control process receives a request for access or modification, the associated action is executed immediately. Let's take a closer look at this process:

Copy Code code as follows:

Func (ACC *concurrentaccount) listen () {
Execute control process
Go func () {
for {
Select {
Case AMNT: = <-acc.deposits:
Acc.account.Deposit (AMNT)
Case AMNT: = <-acc.withdrawals:
Acc.account.Withdraw (AMNT)
Case CH: = <-acc.balances:
CH <-acc.account.Balance ()
}
}
}()
}

The select keeps fetching messages from each channel, and each channel is consistent with what they are doing.

The important point is that everything inside the Select declaration is executed sequentially (queued in the same handler). Only one event (accepted or sent in the channel) occurs at a time, which ensures that shared resources are accessed synchronously.

It's a bit of a circle to understand.

Let's look at the implementation of Balance () with an example:

Copy Code code as follows:

The process of a supplementary card | Control process
----------------------------------------------

1. B.balance () |
2. CH-> [acc.balances]-> Ch
3. <-ch | Balance = Acc.account.Balance ()
4. Return Balance <-[ch]<-Balance
5 |

What did both of these processes do?

The process of the supplementary card

1. Call B.balance ()
2. New channel CH, the CH channel is plugged into the channel acc.balances to communicate with the control flow, so the control flow can also return the balance through CH
3. Waiting for <-ch to obtain the balance to be accepted
4. Acceptance of Balance
5. Continue to

Control process

1. Idle or handling
2. Accept the balance request through the CH channel inside the Acc.balances channel
3. Obtain the true Balance value
4. Send the balance value to the CH channel
5. Prepare to process the next request

The control process handles only one event at a time. This is why, in addition to describing these, there are no 第2-4 steps to perform.

Summarize

This blog describes the problem and the solution to the problem, but did not delve into the pros and cons of the different solutions.

In fact, the example of this article is more suitable for the mutex, because the code is more clear.

In the end, please point out my mistake without scruple!

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.