Golang - goroutine and channel

Posted by peanutbutter on Tue, 25 Jan 2022 02:08:33 +0100

Original address: https://juejin.cn/post/6844903778617917453

goroutine

goroutine is the part of application concurrent processing in Go. It can perform efficient concurrent operations.

  • Coroutines are lightweight and cheaper than threads. It can be created in memory using 4K stack memory.
  • It can split the stack and dynamically increase or reduce the use of memory. Stack management will be automatically released after the process exits.
  • The stack of the coroutine will scale as needed without stack overflow.

Use of synergy

package main

import (
	"fmt"
	"time"
)

func main() {
	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() {
	fmt.Println("Beginning longWait()")
	time.Sleep(5 * 1e9)
	fmt.Println("End of longWait()")
}

func shortWait() {
	fmt.Println("Beginning shortWait()")
	time.Sleep(2 * 1e9)
	fmt.Println("End of shortWait()")
}
Copy code

In go, the go keyword is used to open a coroutine, and the main function can also be regarded as a coroutine.

It is not difficult to understand that 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 code

However, when we set the sleep time of main to 4s, the output changes.

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

The program does not output End of longWait() because longWait() and main() run in different coroutines and are asynchronous. In other words, long before longWait() ends, main has exited, and naturally the output cannot be seen.

channel

Channels are a special data type in Go. They can send typed data to communicate between processes to avoid problems caused by memory sharing.

The communication mode of the channel ensures the synchronization, and only one co process can access the data at the same time without data competition.

Take the conveyor belt of the factory as an example. One machine places goods (producer cooperation process) and passes through the conveyor belt to the next machine for packing (consumer cooperation process).

 

 

 

Use of channels

Before we learn to use pipes, let's look at a "tragedy".

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Reveal romantic feelings...")
	go sendLove()
	go responseLove()
	waitFor()
	fmt.Println("Leaving ☠️....")
}

func waitFor() {
	for i := 0; i < 5; i++ {
		fmt.Println("Keep waiting...")
		time.Sleep(1 * 1e9)
	}
}

func sendLove() {
	fmt.Println("Love you, mm ❤️")
}

func responseLove() {
	time.Sleep(6 * 1e9)
	fmt.Println("Love you, too")
}
Copy code

With the knowledge learned above, it is not difficult to see... It's really miserable

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

Mingming received a response from the girl in secret love, but thought the other party didn't accept his feelings and left in tears. [TAT]

It can be seen how much misunderstanding will be caused if there is no mutual communication between collaborative processes. Fortunately, we have a channel. Now let's rewrite the ending of the story~

package main

import (
	"fmt"
	"time"
)

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

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

	if answer != "" {
		fmt.Println(answer)
	} else {
		fmt.Println("Dead ☠️....")
	}

}

func waitFor() {
	for i := 0; i < 5; i++ {
		fmt.Println("Keep waiting...")
		time.Sleep(1 * 1e9)
	}
}

func sendLove() {
	fmt.Println("Love you, mm ❤️")
}

func responseLove(ch chan string) {
	time.Sleep(6 * 1e9)
	ch <- "Love you, too"
}
Copy code

Output is:

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

everybody ' s happy.

Here, we use ch: = make (Chan string) to create a pipe of string type. Of course, we can also build other types, such as CH: = make (Chan int), or even a function pipe funcchan: = Chan func().

We also use a communication operator < -.

  • Flow channel: ch < - content, send variable content with pipeline ch.

  • Outflow from channel: answer: = < - CH, variable answer receives data from channel ch.

  • < - CH can be called separately to obtain the next value of the channel. The current value will be discarded, but can be used for verification, such as:

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

Channel blocking

  • For the same channel, the sending operation will 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 input, 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 will remain blocked.

The above two properties reflect the characteristics of unbuffered channels: only one data is allowed to exist in the channel at the same time.

Let's feel it through examples:

package main

import "fmt"

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

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

Program output:

0
 Copy code

The pump() function here is called the producer.

Unblock the channel

package main

import "fmt"
import "time"

func main() {
	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 code

Here, we define a suck function as the receiver and give the main coroutine a 1s running time. Therefore, 70W + output [TAT] is generated.

Channel deadlock

The two sections of the channel block each other, which will form a deadlock state. When Go runs, it will check and panic and stop the program. Unbuffered channels are blocked.

package main

import "fmt"

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

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

Obviously, when out < - 2, the main thread is blocked because there is no receiver.

Synchronization channel

In addition to the ordinary cache free channel, there is a special cache Channel - synchronous channel.

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

BUF is the number of elements that the channel can hold at the same time, that is, the buffer size of ch1. The channel will not be blocked until the buf is full.

If the capacity is greater than 0, the channel is asynchronous: before the buffer is full or edge control, the communication will not be blocked, and the elements will be received in the order of sending.

Synchronization: ch: = make (Chan type, value)

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

Using channel buffer can make the program more scalable.

Try to use unbuffered channels in the first place, and only use buffering in case of uncertainty.

package main

import "fmt"
import "time"

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

Copy code

Semaphore mode

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

func main() {
    ch := make(chan int)
    go compute(ch)
    doSomethingElaseForAWhile()
    result := <-ch
}
Copy 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 handle 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 code

Semaphore with buffer channel

Semaphore is a common synchronization mechanism to realize mutual exclusion lock, restrict access to resources and solve the problem of reading and writing.

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

Initialization semaphore

sem = make(semaphore, N)
Copy code

Operate the semaphore and establish a mutual exclusion lock

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 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() {
	stream := pump()
	go suck(stream)
	time.Sleep(1e9)
}

func pump() chan int {
	ch := make(chan int)
	go func() {
		for i := 0; ; i++ {
			ch <- i
		}
	}()
	return ch
}

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

The channel uses a for loop

The for loop can continuously get the value from CH until the channel is closed. (this means that another co process must write to ch and close after writing is completed)

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

import (
	"fmt"
	"time"
)

func main() {
	suck(pump())
	time.Sleep(1e9)
}

func pump() chan int {
	ch := make(chan int)
	go func() {
		for i := 0; ; i++ {
			ch <- i
		}
	}()
	return ch
}

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

Direction of channel

A channel can mean that it only sends or only accepts:

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

The receive only channel (< - Chan T) cannot be closed because the closed channel is used by the sender to indicate that it will no longer send values to the channel, so it is meaningless to the receive only channel.

Pipe and selector mode

Learn this from a classic example of finding prime numbers by sieve method.

 

 

 

The main idea of this algorithm is to introduce the sieve method (an algorithm with time complexity of O(x * ln(lnx)), sort a given returned positive integer from large to small, and then filter out all non prime numbers. Then the smallest of the remaining numbers is prime, remove the multiple of the number, and so on.

Suppose a set of positive integers ranging from 1 to 30 has been sorted from large to small.

The non prime number 1 is screened out for the first time, and then the smallest of the remaining numbers is 2.

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

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

Among the remaining numbers, 3 is the smallest and prime. Take out and remove the multiples of all 3 and cycle until all numbers are screened.

The code is as follows:

// General writing
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
		if i%prime != 0 {
			out <- i
		}
	}
}

func main() {
	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 code
// Customary writing
package main

import (
	"fmt"
)

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

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

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

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


Author:_ Hotown
Link: https://juejin.cn/post/6844903778617917453
Source: Nuggets
The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.

Topics: Go