The basic concepts of coroutines

Notice that the concept of coroutines is different from language to language, but just the concept of go

In short, the coroutine of GO has the following characteristics

Lightweight, much smaller than threads.

The preemptive

What is non-preemptive?

Threads are preemption, because thread scheduling will have the intervention of the operating system, and go coroutines do not belong to OS resources, OS can not schedule, only go programmers have the ability to deal with it, if you do not deal with it, you will not interfere

Multiple coroutines may run on one or more threads

Go coroutine is a compiler level concept, we do not have to go into his principle, beginners just master its use

Here’s an example:

func print(i int) {
   for {
      fmt.Printf("i=%d\n", i)
   }
}

func main() {
   for i := 0; i < 10; i++ {
      go print(i)
   }
   time.Sleep(10 * time.Millisecond)
}
Copy the code

So the output of this program is obviously going to be printing in various ways I =0 all the way up to I =9

channel

Here is a simple channel example

func chanDemo() {
   c := make(chan int)
   go func() {
      for {
         n := <-c
         fmt.Println(n)
      }
   }()
   c <- 1
   c <- 2
   time.Sleep(time.Millisecond)
}

func main() {
   chanDemo()
}
Copy the code

I wrote a 1 and a 2 in c and I printed it in the other goroutine

Next, build a slightly more complex program that builds 10 Chans and prints their own information

func worker(id int, c chan int) {
   for {
      fmt.Println("worker ", id, "received ", string(rune(<-c)))
   }
}

func chanDemo() {

   var chanArray [10]chan int
   for i := 0; i < 10; i++ {
      chanArray[i] = make(chan int)
      go worker(i, chanArray[i])
   }

   for i := 0; i < 10; i++ {
      chanArray[i] <- 'a' + i
   }

   time.Sleep(time.Millisecond)
}
Copy the code

Some people here might ask me why I sent the message in abcdefG order is out of order?

In fact, the printing statement itself is an IO operation, and the IO operation is shared by resources, so there will be resource competition

So the end result is out of order, because it’s not certain who can grab the IO resource, it’s random.

The more explicit chan

As we know from the above code, chan can send and receive, but sometimes we, as the chan provider, want to tell the user exactly what to do when you send or receive a chan.

CreateWorker (id int) chan< -int {c := make(chan int) go func() {for {// But inside Println("worker ", id, "received ", String (rune(<-c)))}}() return c} func chanDemo() {var chanArray [10]chan< -int for I := 0; i < 10; i++ { chanArray[i] = createWorker(i) //go worker(i, chanArray[i]) } for i := 0; i < 10; i++ { chanArray[i] <- 'a' + i } time.Sleep(time.Millisecond) } func main() { chanDemo() }Copy the code

bufferchannel

func bufferChan() {
   c := make(chan int)
   c <- 1
}

func main() {
   //chanDemo()
   bufferChan()
   time.Sleep(time.Millisecond)

}
Copy the code

The program will execute incorrectly because your C is only sending but not receiving, which is not allowed in GO

However, sometimes we do not need to receive the first few sets of data when creating chan, so we can use buffer to process

func bufferChan() {
   c := make(chan int, 3)
   c <- 1
   c <- 2
   c <- 3
}
Copy the code

For example, there is no problem at all because the data we send reaches Chan first

Close the chan

func bufferChan() {
   c := make(chan int, 3)
   go func() {
      for {
         fmt.Println("received ", <-c)
      }
   }()
   c <- 1
   c <- 2
   c <- 3
   close(c)
}
Copy the code

So if you look at the results here,

If we close, we will receive 0. Actually, this is a go feature, so if you turn off chan, chan will keep sending the default value of chan, which in this case is int so it’s going to default to 0,

func bufferChan() { c := make(chan int, 3) go func() { for { v, ok := <-c if ! ok { fmt.Println("closed") break } fmt.Println("received ", v) } }() c <- 1 c <- 2 c <- 3 close(c) }Copy the code

Of course, there’s an even simpler way to write it:

func bufferChan2() {
   c := make(chan int, 3)
   go func() {
      for v := range c {
         fmt.Println("received ", v)

      }
   }()
   c <- 1
   c <- 2
   c <- 3
   close(c)
}
Copy the code

Don’t communicate by sharing memory, communicate by sharing memory

See how to understand the go language’s concurrent design philosophy

We can see from the previous example that we are using time sleep to control the program not to exit. This is pretty rough, and what we want to do is when you’re done printing the program automatically exits. How should this be done?

Type worker struct {in chan int done chan bool} type worker struct {in chan int done chan bool}Copy the code

How do you tell the world we’re done? When the print is finished, send a data to done

func doWorker(worker *Worker) {
   go func() {
      for {
         fmt.Println("receive:", <-worker.in)
         worker.done <- true
      }
   }()
}
Copy the code

How to create a worker?

func createWorker(id int) Worker {
   w := Worker{
      in:   make(chan int),
      done: make(chan bool),
   }
   doWorker(&w)
   return w
}
Copy the code

The creation process is relatively simple

Finally, let’s look at the pivot function

Func main() {var chanArray [10]Worker // Create a Worker for I := 0; i < 10; I ++ {chanArray[I] = createWorker(I)} i < 10; i++ { chanArray[i].in <- i <-chanArray[i].done } }Copy the code

Take a look at the execution result first:

This time we don’t execute time inside main. Sleep but still executes each print statement

But is there something wrong with this print statement?

Why is it sequential?

You have no concurrent ability to execute sequentially

In our for loop, we have a call to done (chan) after we send the data. We won’t continue the loop until we get done, so it’s a sequential operation. Here we’ll write it again:

Func main() {var chanArray [10]Worker // Create a Worker for I := 0; i < 10; I ++ {chanArray[I] = createWorker(I)} i < 10; i++ { chanArray[i].in <- i } for _, w := range chanArray { <-w.done } }Copy the code

This time it became me to serve first, and then I made a loop to accept:

This time the result is correct.

Let’s make it a little more difficult

After sending 10 data this time, we want to send the next 10 data, and the program can only exit after printing all the data sent twice

How to deal with it?

Func main() {var chanArray [10]Worker // Create a Worker for I := 0; i < 10; I ++ {chanArray[I] = createWorker(I)} i < 10; I ++ {chanArray[I]. In < -i} for I := 0; i < 10; i++ { chanArray[i].in <- i + 10 } for _, w := range chanArray { <-w.done <-w.done } }Copy the code

It seems to be correct to send two messages in a row, and then the receiver receives two messages before ending

But after running, the program reported an error, why?

Because we triggered a done write for each worker after the first 0-9 data was sent, but there was no place to receive this done write after the done trigger write, and then we immediately started to write 10-19 to the worker

Here’s a simple fix for this problem:

func doWorker(worker *Worker) {
   go func() {
      for {
         fmt.Println("receive:", <-worker.in)
         go func() {
            worker.done <- true
         }()

      }
   }()
}
Copy the code

Why is this right

Because you’ve re-started a goroutine to write data without getting stuck in your routine.

The easier way to write it is to wait

type Worker struct {
   in chan int
   wg *sync.WaitGroup
}
Copy the code
func createWorker(id int, group *sync.WaitGroup) Worker {
   w := Worker{
      in: make(chan int),
      wg: group,
   }
   doWorker(&w)
   return w
}

func doWorker(worker *Worker) {
   go func() {
      for {
         fmt.Println("receive:", <-worker.in)
         worker.wg.Done()
      }
   }()
}
Copy the code

In fact, the official waitgroup is used to do the waiting

Func main() {var wg sync.waitgroup var chanArray [10]Worker // create a Worker for I := 0; i < 10; I ++ {chanArray[I] = createWorker(I, &wg)} wg.add (20) i < 10; I ++ {chanArray[I]. In < -i} for I := 0; i < 10; i++ { chanArray[i].in <- i + 10 } wg.Wait() }Copy the code

“Add” means 20 tasks have been completed, and “wait” means to wait until all 20 tasks have been completed.