Hello, I am discrete. 🌞

With the Use of Golang Goroutine and Channel, a lot of people, myself included, have a hard time figuring out how it works at first.

Problem description

I won’t go into the details of what goroutine and channel are, but if you don’t know, you don’t have to read this summary.

Go straight to the code, what do you think the output of this code is going to be?

func main(a) {
   go func(a) {
      res := ""
      for i := 0; i < 5; i++ {
         res += strconv.Itoa(i)
      }
      fmt.Println(res)
   }()

   fmt.Println("Done")}Copy the code

A lot of people would say yes

0 1 2 3 4 
Done
Copy the code

However, the answer is not, because our sub Goroutine ends before we can run main Goroutine, so we just print a Done and the whole program ends.

Is there a way to make main Goroutine wait for the Sub Goroutine to run before printing the Done? We can tell main Goroutine to wait a second.

func main(a) {
   go func(a) {
      res := ""
      for i := 0; i < 5; i++ {
         res += strconv.Itoa(i) + ""
      }
      fmt.Println(res)
   }()

   time.Sleep(1*time.Second)
   fmt.Println("Done")}Copy the code

But is it a good way? While the Sub Goroutine might have run outin a millisecond, we made the Main Gouroutine wait a full second for nothing. Can we make main Goroutine run as soon as sub Goroutine runs?

Of course, we just need to set up a flag for main Goroutine to check.

func main(a) {
   flag := false
   go func(a) {
      res := ""
      for i := 0; i < 5; i++ {
         res += strconv.Itoa(i) + ""
      }
      fmt.Println(res)
      flag = true} ()for! flag { time.Sleep(1*time.Millisecond)
   }
   fmt.Println("Done")}Copy the code

But then there’s the question, what is it?

There is a popular saying in the Golang community called

Do not communicate by sharing memory; instead, share memory by communicating.
Copy the code

Instead of communicating by sharing memory, you should communicate by sharing memory.

Why is that? Because shared memory is used in multi-threaded environments, locks are required to prevent a variable from being changed by multiple threads at the same time. Here, we still have only one flag, assuming that there are multiple flags, it is easy to deadlock.

This leads to the concept of a channel, a channel in Golang that guarantees that only one Gotoutine can access its data at a time.

But even then, you’re just switching from sharing memory to channel-based communication.

A channel can be used to circulate data, which means that different threads can exchange information with each other, one sending and one receiving. The memory is actually shared through channel communication.

So we can solve the above problems as follows:

func main(a) {
   channel := make(chan int)
   go func(a) {
      res := ""
      for i := 0; i < 5; i++ {
         res += strconv.Itoa(i) + ""
      }
      fmt.Println(res)
      channel <- 1
   }()

   <- channel
   fmt.Println("Done")}Copy the code

A lot of people write channel code and it’s very easy to make a mistake, that is

fatal error: all goroutines are asleep - deadlock!
Copy the code

For example, in the following code, some people will be confused as to why I can’t save one and take another.

func main(a) {
   channel := make(chan int)
   channel <- 1
   <- channel
}
Copy the code

I’ll sum this up in three words:

  1. Channel acts as an intermediate station and has its own inventory. Channels are divided into unbuffered channels and buffered channels. The former has an inventory of 1, and the latter has an inventory of initial size plus 1

  2. When a Goroutine tries to fill a channel, the gouroutine blocks immediately.

  3. When a Goroutine tries to fetch something from a channel immediately and finds nothing in it, the goroutine blocks immediately.

The following code is my colleague’s code of Review in my actual work

func main(a) {
   wg := sync.WaitGroup{}
   wg.Add(2)

   channel := make(chan error, 1)

   go func(a) {
      defer wg.Done()
      channel <- errors.New("err1")
   }()

   go func(a) {
      defer wg.Done()
      channel <- errors.New("err2")
   }()

   wg.Wait()
   close(channel)

   select {
   case err := <- channel:
      iferr ! =nil {
         fmt.Println(err)
      }

   }
}
Copy the code

At that time, I asked him if only 1 deadlock was within your range. My reason was that if Sub Goroutine 1 ran first and Then Sub Goroutine 2 ran, then no deadlock would be left. After reading my summary, I felt that I was a fool. It seems that their summary of things also want to review more on the line, really easy to forget.

The correct explanation is that sub Goroutine2 will block, and main Goroutine will take the ERR out.

The main Goroutine must wait for the two sub goroutines to run before running the select section.

It is important to note that the Main Goroutine will always end with only one err, but this is my colleague’s intention to see any err and fail.

conclusion

  1. Channel acts as an intermediate station and has its own inventory. Channels are divided into unbuffered channels and buffered channels. The former has an inventory of 1, and the latter has an inventory of initial size plus 1

  2. When a Goroutine tries to fill a channel, the gouroutine blocks immediately.

  3. When a Goroutine tries to fetch something from a channel immediately and finds nothing in it, the goroutine blocks immediately.

I hope readers can learn something from this article. May I be as rich as my Homie.