The distance from 14 articles is still 4, I want to refueling!! Tea urn!!!!! Here I come!!

preface

Earlier I talked about concurrency in Go, but I opened up the most troublesome aspect of concurrency in Go: reading and writing resources. Next, I will talk about how to solve concurrency issues in Go.

Channel is the channel

In the Go language, there is a rule: Don’t communication through the Shared memory, but should be through the communication to the Shared memory, actually this sentence with Java, for example, we have been in contact with the PHP language is not the same, most of the time, method of communication between the two threads want to, through access to a Shared memory, to complete the communication between threads, but puts forward, Go through the communication to the Shared memory, The key to this is the channel

A channel sets up a pipeline when a resource needs to be shared and provides mechanisms to ensure that data is exchanged synchronously.

When declaring a channel, you first need to specify the type of data flowing through the channel, which is created using the built-in make function.

	a := make(chan int) // No buffered channel
	b := make(chan string.10) // There are buffer channels
Copy the code

As you can see from the above code, there are two types of channel, one is buffered and the other is unbuffered. I will explain the difference later.

If you want to store/fetch data into the channel, you use the <- method

	a := make(chan int)
	a <- 10  / / variable
	b := <- a // Fetch the variable
Copy the code

The key difference, whether a channel is buffered or not, is the process of accessing variables.

Unbuffered channel

An unbuffered channel means a channel that does not save data when it receives it. This channel must be completed simultaneously with the sending and receiving goroutine, otherwise it will block. Neither of these operations can be done without the other.

The figure above shows how two Goroutines share a value over a channel.

Step 1: Create a channel between two Goroutines. Step 2: The left Goroutine puts resources into the channel and locks the left Goroutine. Step 3: The right Goroutine puts its hand into the channel and locks the right goroutine. Step 4 and Step 5 Complete resource sharing and exchange, and Step 6 complete unlocking.

Note: when two Goroutines share resources over an unbuffered channel, the receiving goroutine blocks in front of the channel first!

To better illustrate this, let’s use a simple code to illustrate the problem.

var w sync.WaitGroup

func main(a) {
	count := make(chan int) // Create an unbuffered channel

	w.Add(2) // Add the counter by two so that both goroutines can be executed

	go play(ball, "min")// Contestant Number one
	go play(ball, "hong")// Contestant Number two
	ball <- 1
	w.Wait()
}

func play(count chan int, name string) {
	defer w.Done()
	for {
		it, ok := <-count
		if! ok { log.Printf("game over %s win",name)
			return
		}
		n := rand.Intn(100)
		if n%15= =0 {
			log.Printf("game over %s lose",name)
			close(count)
			return
		}
		log.Printf("round is %d",it)
		it++

		count <- it
	}
}
Copy the code

In the above example, I used the Goroutine to simulate a fight between two players, where you punch and I punch until one of them falls. The rule is to generate a random number inside the play function. When the random number is divided by 15 and the remainder is 0, the channel is closed and the player loses. The other player knew he had won when he was notified that the channel was closed.

In the process, the rounds of the fight are added up until one side is down.

So the output is going to look something like this

2021/04/22 02:51:17 round is 1
2021/04/22 02:51:17 round is 2
2021/04/22 02:51:17 round is 3
2021/04/22 02:51:17 round is 4
2021/04/22 02:51:17 round is 5
2021/04/22 02:51:17 round is 6
2021/04/22 02:51:17 round is 7
2021/04/22 02:51:17 round is 8
2021/04/22 02:51:17 round is 9
2021/04/22 02:51:17 game over min lose
2021/04/22 02:51:17 game over hong win
Copy the code

This is called a Goroutine leak. This can be a bug, but it’s important to note that unlike the garbage collection of variables, the process of goroutine contribution must be synchronized. If one side is slow, the other side is in a race. Leaked Goroutines are not recycled.

Now, let’s take a look at what happens to the buffered channel

There is a buffer channel

There is a buffer channel, which, as the name implies, is a channel that can store a certain number of values. Instead of requiring both the receiver and the sender to be ready at the same time, to block both the sending and receiving of two Goroutines, the sending Goroutine must satisfy that the channel is full and then cram data into it. The sender is blocked, and the receiver’s goroutine is blocked because there is no data left in the channel.

In fact, you can understand it this way, it is a bit like making bread in a bakery. One master is responsible for rolling the dough, one is responsible for making the buns, and one is responsible for putting the buns into the cage. The three masters represent the three goroutines. Because there is a buffer zone between him and the rolling pastry master who has been separately installed, temporarily storing some raw materials, and the rolling pastry master will not stop because the baozi master stopped, the whole process will stop, they can still continue to work, which is the concept of no buffer channel.

As shown in the figure above, the left and right Goroutine do not need to put their hands into the channel at the same time to complete the transmission, and they are non-blocking until the cache is full.

Again, let’s look at a concrete example here

var w sync.WaitGroup

func main(a) {
	a := make(chan int , 5) // Generate two buffer channels of size 5
	b := make(chan int , 5)

	w.Add(3)

	go work1(a)
	go work2(a,b)
	go work3(b)

	w.Wait()
}

func work1(a chan<- int)  {
	defer w.Done()
	for  {
		if len(a) == 5 {
			log.Print("work1 over")
			close(a)
			return
		}
		a <- rand.Int()
	}
}

func work2(a <-chan int, b chan<- int)  {
	defer w.Done()
	for  {
		time.Sleep(time.Second)
		if len(a) == 0 {
			log.Print("work2 over")
			close(b)
			return
		}
		c := <-a
		log.Print(c)
		b <- c
	}
}

func work3(b <-chan int)  {
	defer w.Done()
	for {
		if len(b)>0 {
			<-b
		}
		if_ , ok := <-b; ! ok { log.Print("work3 over")
			return}}}Copy the code

Here is an example of simulation said before baker, the baker made a delay in the middle of operation, which means work2’s work efficiency is slow, you can see, after finished the work when work1, it shut down a passage, and work2 is every second with data from a channel, and in the b channel, Work3 empties the data in channel B. When channel B closes, work3 exits.

If you want work3 to exit as soon as possible, add a goroutine to work2. If you want work3 to exit as soon as possible, add a goroutine to work2.

Whether to choose a buffered channel or an unbuffered channel depends on each person’s business needs.

Multiplexing of select

Now consider a scenario where we use two goroutines to complete the countdown function of 🚀. When the program starts, in five counts, Goroutine one will send a signal to Goroutine two, and then goroutine will complete the task of launching. Obviously we know that we need to have an unbuffered channel, and if the sender and the receiver are not synchronized, one of them will block, and that will leak the Goroutine, which is not what we want to see.

To solve this problem, you need to use a multiplexing feature, called SELECT

Look at the code below

func main(a) {
	var w sync.WaitGroup
	w.Add(2)
	char := make(chan int)

	go func(a) {
		defer w.Done()
		t := 0
		for  {
			time.Sleep(time.Second)
			if t == 5 {
				char <- 1
				return
			}else {
				t += 1}}} ()go func(a) {
		defer w.Done()
		for  {
			time.Sleep(time.Second)
			select {
			case <- char:
				log.Print(Emission!!)
				return
			default:
				log.Print("wait")
			}
		}
	}()
	w.Wait()
}
Copy the code

As you can see in the second Goroutine, to avoid bugs caused by channel blocking, select is used. When select has a default value, it becomes a non-blocking multiplexer. When the program runs, it runs like this

2021/04/22 15:13:54 wait 2021/04/22 15:13:55 wait 2021/04/22 15:13:56 wait 2021/04/22 15:13:57 wait 2021/04/22 15:13:58 Wait 2021/04/22 15:13:59 wait 2021/04/22 15:14:00 emission!!Copy the code

As you can see, select will wait when there is a case that satisfies the condition to execute, it will execute it, and when there is no case, it will wait forever (if there is no default). This is actually a little bit like the switch in JavaScript, so I think that should get the idea.

When there are multiple cases satisfying the conditions simultaneously, it will randomly select one of them for execution, so as to ensure that each case can be executed fairly.

Some of the summary

  1. In Go — don’t communicate through shared memory, communicate through shared memory
  2. Without buffered channels, the receiver and the sender must be synchronized, otherwise one side will block and the Goroutine will leak
  3. There are buffer channels to avoid this to some extent, but there is no way to synchronize the data
  4. Multiplexing A select is something a bit like a switch, but when there are multiple cases that satisfy the criteria, it randomly selects one of them to execute

Another thing to note is that passing a nil variable to a chan directly blocks the channel.

At the end

I’m getting an upgrade!! If you see here, ask for a praise!!