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