The problem

In Go, threads communicate in two ways. One is shared memory mode and the other is message communication mode. Message communication has four elements: sender, receiver, channel, and message. The sender and receiver communicate by passing messages through channels. Communication is at the heart of collaboration between threads. If communication fails, both sender and receiver will be blocked, resulting in a deadlock.

Let’s look at the following question:

/ / the sender
func main(a) {
	ch := make(chan int)
    
	ch <- 1 / / send
	fmt.Println("Send".1)

	/ / the recipient
	go func(a) {
		v := <-ch / / receive
		fmt.Println("Receive", v)
	}()
}
Copy the code

What do they output?

Send A.1; receive1
B. deadlock
Copy the code

The solution

If you execute the code, you will find an error in the program, resulting in deadlock. Why is it deadlocked? Because the unbuffered channel is used. In Go, there are two types of channels, no cache channel and cached channel. Use no cache channel, a little careless will deadlock.

No cache channel indicates that there is no cache space. If there is a cache channel, there is a cache space. In addition to cache space, the blocking strategy is different.

In a caches free channel, the sender’s send operation blocks until the receiver performs the accept operation. Similarly, the receiver’s accept operation blocks until the sender performs the send operation. The sender’s send operation is synchronized with the receiver’s receive operation.

With a cache channel, if the cache space is full, the sender’s send operation will block. The sender does not continue until the receiver retrieves the data and the cache space is available. If the cache is empty, the receiver’s accept operation will block.

In the original problem, the sender’s send operation was blocked because of the no-cache channel. The statement following the send operation will not be executed. There are two ways to do it without blocking.

The first is to use a caches free channel where the receiver is created and then the data is sent. The code is as follows:

func main(a) {
	ch := make(chan int)

	/ / the recipient
	go func(a) {
		v := <-ch // this is blocked
		fmt.Println("Received:", v)
	}()

	/ / the sender
	ch <- 1
	fmt.Println("Send:".1)}Copy the code

The second is to use a cached channel. As for creating recipients, the order in which the send operations are performed becomes irrelevant. The code is as follows:

func main(a) {
	ch := make(chan int.1)
    
    / / the sender
	ch <- 1
	fmt.Println("Send:".1)

	/ / the recipient
	go func(a) {
		v := <-ch // this is blocked
		fmt.Println("Received:", v)
	}()
}
Copy the code

The principle of

These problems are already described in the operating system process communication model. In operating system process communication, though, the sender and receiver are processes, not threads, not Goroutines. But from a communications point of view, there is no difference. Communication is at its core, whether it is between processes or threads. In the operating system, inter-process message communication can be done by send() and receive(). Message communication, which can be blocking or non-blocking, is also called synchronous or asynchronous.

Blocking and non-blocking are considered by many developers to have different meanings from synchronous and asynchronous, and are widely discussed. However, contrary to popular belief, blocking and non-blocking mean the same as synchronous and asynchronous.

Therefore, it is meaningless to discuss the difference between blocking and non-blocking and synchronous and asynchronous. Because they are inherently symmetric concepts (that is, different words express the same concept). What’s the point of talking about it? It makes more sense to discuss whether the sender blocks or the receiver blocks. Blocking, non-blocking, sender and receiver are combined into a two-by-two matrix:

During communication, there are four states:

  • Sender blocking: The sender blocks until the message is received by the receiver (or mailbox).
  • Sender non-blocking: The sender sends the message and continues operation. The recipient status is not associated.
  • Receiver block: The receiver blocks until a message is available.
  • Receiver non-blocking: The receiver receives a valid message or an empty message.

The blocking state of sender and receiver is something you often think about in programming.

The principle of

How do I properly use Go channels to communicate? Determine by channel type. If a caches free channel is used, a sender or receiver is created before the send or receive operation is performed. If you use a cache channel, note that full cache space blocks the send operation.

In addition to judging by channel type, the most important thing is to be familiar with the relationship between sender, receiver, and channel, and when a send or receive operation is blocked. In Go, you care which Goroutine is the sender and which goroutine is the receiver? You also have to look at what is the channel between the sender and the receiver? Finally, when do send and receive block?

Together, these are called message communication patterns.

reference

  • Golang bible
  • Operating System Concepts