Goroutine

The goroutine is the concurrent processing part of the application in Go, which can perform efficient concurrent operations.

  • Coroutines are lightweight and cheaper than threads. It can be created in memory using 4K stack memory.
  • The ability to split stacks dynamically to increase or decrease memory usage. Stack management is automatically released when the coroutine exits.
  • The stacks of coroutines are scaled as needed without stack overflow.

Use of coroutines

package main

import (
	"fmt"
	"time"
)

func main(a) {
	fmt.Println("In main()")
	go longWait()
	go shortWait()
	fmt.Println("About to sleep in main()")

	//time.Sleep(4 * 1e9)
	time.Sleep(10 * 1e9)
	fmt.Println("At the end of main()")}func longWait(a) {
	fmt.Println("Beginning longWait()")
	time.Sleep(5 * 1e9)
	fmt.Println("End of longWait()")}func shortWait(a) {
	fmt.Println("Beginning shortWait()")
	time.Sleep(2 * 1e9)
	fmt.Println("End of shortWait()")}Copy the code

Go uses the Go keyword to open a coroutine, where main can also be considered a coroutine.

Understandably, the output of the above code is:

In main()
About to sleep in main()
Beginning shortWait()
Beginning longWait()
End of shortWait()
End of longWait()
At the end of main()
Copy the code

However, when we set the sleep time of Main to 4s, the output changed.

In main()
About to sleep in main()
Beginning shortWait()
Beginning longWait()
End of shortWait()
At the end of main()
Copy the code

The program does not print End of longWait() because longWait() and main() run in different coroutines and are asynchronous. That is, long before the end of longWait(), main will have exited and the output will not be visible.

Channel

Channels are a special data type in Go that can send typed data to communicate between coroutines, avoiding memory sharing problems.

The communication mode of the channel ensures synchronization, and only one coroutine can access data at a time, without data contention.

In the case of a factory conveyor belt, one machine places an item (producer coroutine), which passes through the belt to the next machine to pack the case (consumer coroutine).

Use of channels

Before we learn how to use pipes, let’s look at a tragedy.

package main

import (
	"fmt"
	"time"
)

func main(a) {
	fmt.Println("Reveal romantic feelings...")
	go sendLove()
	go responseLove()
	waitFor()
	fmt.Println("The brigade ☠ ️...")}func waitFor(a) {
	for i := 0; i < 5; i++ {
		fmt.Println("Keep waiting...")
		time.Sleep(1 * 1e9)}}func sendLove(a) {
	fmt.Println("Love you, mm ❤ ️")}func responseLove(a) {
	time.Sleep(6 * 1e9)
	fmt.Println("Love you, too")}Copy the code

From what has been learned above, it is not difficult to see that… Really miserably…

Reveal romantic feelings...
Love you, mm ❤️
Keep waiting...
Keep waiting...
Keep waiting...
Keep waiting...
Keep waiting...
Leaving ☠️....
Copy the code

Clearly received a secret crush on the girl’s response, but thought the other side does not accept their feelings, tears leave. “TAT”

You can see how confusing it can be when coroutines don’t talk to each other. Fortunately, we have channel, now let’s rewrite the ending of the story ~

package main

import (
	"fmt"
	"time"
)

func main(a) {
	ch := make(chan string)
	var answer string

	fmt.Println("Reveal fomantic feelings...")
	go sendLove()
	go responseLove(ch)
	waitFor()
	answer = <-ch

	ifanswer ! ="" {
		fmt.Println(answer)
	} else {
		fmt.Println("Dead ☠️....")}}func waitFor(a) {
	for i := 0; i < 5; i++ {
		fmt.Println("Keep waiting...")
		time.Sleep(1 * 1e9)}}func sendLove(a) {
	fmt.Println("Love you, mm ❤ ️")}func responseLove(ch chan string) {
	time.Sleep(6 * 1e9)
	ch <- "Love you, too"
}
Copy the code

The output is:

Reveal fomantic feelings...
Love you, mm ❤️
Keep waiting...
Keep waiting...
Keep waiting...
Keep waiting...
Keep waiting...
Love you, too
Copy the code

Everybody’s happy.

Here we create a pipe of type string using ch := make(chan string), but we can also build other types such as ch := make(chan int), or even a pipe funcChan := chan func().

We also use a communication operator <-.

  • Stream channel: ch < -content, using the pipe CH to send the variable content.

  • Outbound from channel: answer := < -ch, variable answer Receives data from channel CH.

  • < -ch can be called separately to get the next value of the channel. The current value is discarded, but can be used for validation, such as:

    if<- ch ! =100 {
        /* do something */
    }
    Copy the code

Channel blocking

  • For the same channel, the send operation does not end until the receiver is ready. This means that if an unbuffered channel has no space to receive data, new input data cannot be entered, that is, the sender is blocked.
  • For the same channel, the receive operation is blocked until the sender is available. If there is no data in the channel, the receiver remains blocked.

The above two properties reflect the nature of unbuffered channels: only one data can exist in a channel at a time.

Let’s use an example to get a feel for it:

package main

import "fmt"

func main(a) {
	ch1 := make(chan int)
	go pump(ch1)
	fmt.Println(<-ch1)
}

func pump(ch chan int) {
	for i := 0; ; i++ {
		ch <- i
	}
}
Copy the code

Program output:

0
Copy the code

The pump() function here is called the producer.

Unblocking a channel

package main

import "fmt"
import "time"

func main(a) {
	ch1 := make(chan int)
	go pump(ch1)
	go suck(ch1)
	time.Sleep(1e9)}func pump(ch chan int) {
	for i := 0; ; i++ {
		ch <- i
	}
}

func suck(ch chan int) {
	for {
		fmt.Println(<-ch)
	}
}
Copy the code

Here we define a suck function as the receiver and give the main coroutine a 1s run time, resulting in 70W+ output [TAT].

Channel a deadlock

Two sections of the channel block each other, creating a deadlock. When Go runs, it checks and panic, stopping the program. The unbuffered channel will be blocked.

package main

import "fmt"

func main(a) {
	out := make(chan int)
	out <- 2
	go f1(out)
}

func f1(in chan int) {
	fmt.Println(<-in)
}
Copy the code
fatal error: all goroutines are asleep - deadlock!
Copy the code

When out < -2, the main thread is blocked because there are no recipients.

Synchronous channel

In addition to the normal no cache channel, there is a special channel with cache – synchronous channel.

buf := 100
ch1 := make(chan string, buf)
Copy the code

Buf is the number of elements a channel can hold at the same time, that is, the buffer size of CH1. The channel will not block until BUF is full.

If the capacity is greater than zero, the channel is asynchronous: communication does not block until the buffer is fully loaded or sidelined, and elements are received in the order they were sent.

Ch := make(chan type, value)

  • Value ==0 -> synchronous, unbuffered (blocked)
  • Value > 0 –> asynchronous, buffered (non-blocking) depends on the value element

Using channel buffering can make programs more scalable.

Try to use unbuffered channels in the first place, and only use buffering when in doubt.

package main

import "fmt"
import "time"

func main(a) {
	c := make(chan int.50)
	go func(a) {
		time.Sleep(15 * 1e9)
		x := <-c
		fmt.Println("received", x)
	}()
	fmt.Println("sending".10)
	c <- 10
	fmt.Println("send".10)}Copy the code

Semaphore mode

func compute(ch chan int) {
    ch <- someComputation()
}

func main(a) {
    ch := make(chan int)
    go compute(ch)
    doSomethingElaseForAWhile()
    result := <-ch
}
Copy the code

The coroutine processes the end signal by placing a value in channel CH. The main thread waits for <-ch until it gets a value from it.

We can use it to deal with slice sorting:

done := make(chan bool)

doSort := func(s []int) {
    sort(s)
    done <- true
}
i := pivot(s)
go doSort(s[:i])
go doSort(s[i:])
<-done
<-done
Copy the code

Implement semaphores with buffered channels

A common synchronization mechanism that implements mutex to limit access to resources and solve read and write problems.

  • The capacity of the buffered channel must be the same as that of the synchronized resource
  • The length of the channel (the number of elements currently held) is the same as the number of resources currently in use
  • The capacity minus the length of the channel equals the number of unprocessed resources
// Create a channel with variable length but zero capacity
type Empty interface {}
type semaphore chan Empty
Copy the code

Initialize the semaphore

sem = make(semaphore, N)
Copy the code

Operates on a semaphore to establish a mutex

func (s semaphore) P (n int) {
    e := new(Empty)
    for i := 0; i < n; i++ {
        s <- e
    }
}

func (a semaphore) V (n int) {
    for i := 0; i < n; i++ {
        <- s
    }
}

/* mutexes */
func (s semaphore) Lock() {
	s.P(1)
}

func (s semaphore) Unlock(){
	s.V(1)
}

/* signal-wait */
func (s semaphore) Wait(n int) {
	s.P(n)
}

func (s semaphore) Signal() {
	s.V(1)
}
Copy the code

Channel factory mode

Instead of passing a channel as an argument, a channel is generated within the function and returned.

package main

import (
	"fmt"
	"time"
)

func main(a) {
	stream := pump()
	go suck(stream)
	time.Sleep(1e9)}func pump(a) chan int {
	ch := make(chan int)
	go func(a) {
		for i := 0; ; i++ {
			ch <- i
		}
	}()
	return ch
}

func suck(ch chan int) {
	for {
		fmt.Println(<-ch)
	}
}
Copy the code

The channel uses a for loop

The for loop can continue to fetch values from ch until the channel closes. (This means that another coroutine must be written to ch and closed when the writing is complete)

for v := range ch {
    fmt.Println("The value is", v)
}
Copy the code
package main

import (
	"fmt"
	"time"
)

func main(a) {
	suck(pump())
	time.Sleep(1e9)}func pump(a) chan int {
	ch := make(chan int)
	go func(a) {
		for i := 0; ; i++ {
			ch <- i
		}
	}()
	return ch
}

func suck(ch chan int) {
	go func(a) {
		for v := range ch {
			fmt.Println(v)
		}
	}()
}
Copy the code

Direction of passage

A channel may indicate that it sends or receives only:

var send_only chan<- int    // channel can only send data
var recv_only <-chan int    // channel can only receive data
Copy the code

A receive-only channel (<-chan T) cannot be closed because a closed channel is used by the sender to indicate that it is no longer sending a value to the channel, so it makes no sense for a receive-only channel.

Pipe and selector modes

Learn from a classic example of sifting prime numbers.

The main idea of this algorithm is to introduce sieving (an O(x * ln(LNX)) algorithm), sort a given returned positive integer from the largest to the smallest, then filter out all non-prime numbers, so that the smallest of the remaining numbers is prime, then remove multiples of that number, and so on.

Suppose a set of positive integers ranging from 1 to 30 has been sorted from largest to smallest.

The first filter out the nonprime number 1, and then the smallest remaining number is 2.

Since 2 is a prime number, take it out and then remove all multiples of 2, so the remaining number is:

3 5 7 9 11 13 15 17 19 21 23 25 27 29

Of the remaining numbers, 3 is the smallest and prime. Remove and remove all multiples of 3, and loop until all numbers are sifted.

The code is as follows:

//
package main

import (
	"fmt"
)

func generate(ch chan int) {
	for i := 2; i < 100; i++ {
		ch <- i
	}
}

func filter(in, out chan int, prime int) {
	for {
		i := <-in
		ifi%prime ! =0 {
			out <- i
		}
	}
}

func main(a) {
	ch := make(chan int)
	go generate(ch)
	for {
		prime := <-ch
		fmt.Print(prime, "")
		ch1 := make(chan int)
		go filter(ch, ch1, prime)
		ch = ch1
	}
}
Copy the code
// Use the following method
package main

import (
	"fmt"
)

func generate(a) chan int {
	ch := make(chan int)
	go func(a) {
		for i := 2; ; i++ {
			ch <- i
		}
	}()
	return ch
}

func filter(in chan int, prime int) chan int {
	out := make(chan int)
	go func(a) {
		for {
			ifi := <-in; i%prime ! =0 {
				out <- i
			}
		}
	}()
	return out
}

func sieve(a) chan int {
	out := make(chan int)
	go func(a) {
		ch := generate()
		for {
			prime := <-ch
			ch = filter(ch, prime)
			out <- prime
		}
	}()
	return out
}

func main(a) {
	primes := sieve()
	for {
		fmt.Println(<-primes)
	}
}
Copy the code