This is mainly package method, interface, concurrency three content

1. Golang method

Golang is an interface oriented language. Methods here are not methods in the OOM. To learn about Golang’s interface, we need to know the methods first, because interfaces are collections of method declarations.

1.1 Concept of method

  • Methods are used to show and maintain the state of the object itself (forgive me for using the word object, although it might be inappropriate in Go, but for ease of understanding).
  • Methods have associated state, whereas functions usually do not.
  • Method does not support overload, and there is no limit to the receiver parameter name
  • You can define methods (base types, custom types, and even packages!) for types other than interfaces and Pointers.
  • The definition of a method differs from that of a function in that the former has a front instance that receives an argument (receiver), which the compiler uses to determine the type of a method.

Let’s use examples to understand him further

package main

import "fmt"

type N int
/* In object-oriented programming, we use this.method() in the class definition of an object's method. In some languages, the method is implicitly called with this instance parameter, although it is not explicitly defined. In go we use receiver to determine which type the method belongs to */
// If the method does not reference the instance internally, omit the parameter name and keep only the type
func (N) test(a){
    println("hi")}// If N is passed, the compiler interprets it as * N
func (n N) value(a) {
	n++
	fmt.Printf("address:%p,value:%v\n", &n, n)
}
// If n is passed in, the compiler interprets it as &n
func (n *N) pointer(a) {
	(*n)++
	fmt.Printf("address:%p,value:%v\n", n, *n)
}
func Methodtest(a) {
	var n N = 10
	p := &n
        fmt.Printf("n:%p,value:%v\n", &n, n)
	n.value()
	n.pointer()

	p.value()
	p.pointer()
}
//output
n:0x118a007c,value:10      
address:0x114120e0,value:11
address:0x114120bc,value:11
address:0x114120e4,value:12
address:0x114120bc,value:12
Copy the code

Notice that my method defines a receiver that I can pass either an object or a pointer to an object, and vice versa! Of course, the value of a pointer can’t be nil, or any address that has no value. It can be called with an instance value or a pointer, which the compiler automatically switches based on the recerver type of the method. It is worth noting that the same is true for structs, and that the pointer and value object compiler for structs automatically converts them to the desired way

1.1.1 How to select the receiver type of the method

  • To change the instance state *T
  • There is no need to modify the small object of state or the fixed value T
  • Use *T for large objects to reduce replication costs
  • For pointer wrapped objects that reference types, strings, functions, etc., use T directly
  • If synchronization fields such as MuTex are included, use *T to avoid invalid lock caused by replication
  • *T is used for all other inconclusive cases

Override of the 1.1.2 method

package main

import "fmt"

type usr struct{}
type manager struct {
	u1 usr
}

func (usr) toString(a) string {
	return "user"
}
func (manager) toString(a) string {
	return "manager"
}
func anonymousTest(a) {
	var m manager
	fmt.Println(m.toString())
	fmt.Print(m.u1.toString())
}
//output
manager
user
Copy the code

As you can see, member usr has a method with the same name as Manager, so it overwrites it, using its own method directly, so there is no overloading, but there is overwriting! But although members’ methods are accessible, they are not inherited, just overridden!

1.1.3 Methods to Access Anonymous Members

// If a member variable inside manager is anonymous, how do I call its methods?
package main

import "fmt"

type usr struct{}
type manager struct {
	usr
}

func (usr) toString(a) string {
	return "user"
}
func (manager) toString(a) string {
	return "manager"
}
func anonymousTest(a) {
	var m manager
	fmt.Println(m.toString())
	fmt.Print(m.usr.toString())
}
// If you tell it the member type of the structure, the compiler automatically looks for the member
Copy the code

A type has a set of methods associated with it, which determines whether it implements an interface

  • The type T method set contains all receiver T methods
  • The type *T method contains all receiverT+*T methods
  • If S is embedded anonymously in T, the method set of T contains the receiverT+S method
  • The method set of T contains receiverT+S+*S methods
  • Anonymously embedded S in T orThe method set of S and T will contain receiverT+S+S+ T * method
package main

import (
	"fmt"
	"reflect"
)

type struct1 struct {
	struct2
}
type struct2 struct {}
func (struct1) ValMethod1(a){}
func (*struct1) PointMethod1(a){}
func (struct2) ValMethod2(a){}
func (*struct2) PointMethod2(a){}

func methodSet(a interface{}){
            t:=reflect.TypeOf(a)                             // Returns the names of all methods in the method set
	for i,num:=0,t.NumMethod(); i<num; i++{ m:=t.Method(i) fmt.Println(m.Name,m.Type) } }func reflectTest(a){
	var stu1 struct1
	// Call member methods with values
	methodSet(stu1)
	fmt.Println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --")
	// Call member methods with Pointers
	methodSet(&stu1)
}
//output
ValMethod1 func(main.struct1)
ValMethod2 func(main.struct1)
---------------------------
PointMethod1 func(*main.struct1)
PointMethod2 func(*main.struct1)
ValMethod1 func(*main.struct1)
ValMethod2 func(*main.struct1)
Copy the code

1.2 Method Expression

Methods, like functions, can be copied to variables, or passed as arguments, in addition to being called directly. 1. Method expression: This is the argument referenced by type, in which case receiver is the first argument, and is called using explicit arguments. Based on pointer or instance references, the parameter signature does not sound changed, and the receiver object required by the method is immediately evaluated and copied, bound to it for later execution. The receiver parameter can be passed as shown in the following example

package main

import "fmt"

type NN int
func (n NN) test(a){
	fmt.Println("address of n:",&n)
}
func methodExpression(a){
        //method expression
	var n NN=25
	fmt.Println("n's address is",&n)
	f1:=NN.test
	f1(n)
	fmt.Println("-- -- -- -- -- -- -- -- -- -- -- -- --")
	f2:=(*NN).test
	f2(&n)
	fmt.Println("-- -- -- -- -- -- -- -- -- -- -- -- --")
	NN.test(n)
	(*NN).test(&n)
	// A receiver passed to a method using an expression is passed to a method variable exactly as the method signature
	//(*NN).test(n)
	//(NN).test(&n)
        
        //method value
	fmt.Println("-- -- -- -- -- -- -- -- -- -- -- -- --")
	f3:=n.test
	f3()
	fmt.Println("-- -- -- -- -- -- -- -- -- -- -- -- --")
	f4:=(&n).test
	f4()

}
Copy the code

2. Golang interface

An interface is a collection of method signatures. A method whose receiver is *n does not implement the interface if it belongs to an interface

2.1 Interface and implicit implementation

A type implements an interface by implementing all of its methods. Since there is no special explicit declaration, there is no “implements” keyword. Implicit interfaces decouple the definition from the implementation of the interface so that the implementation of the interface can appear in any package without prior preparation. So the empty interface can be implemented as if it’s all types, and the interface value takes over all data types even if the actual value in the interface is nil, the method will still be called by the nil receiver. But the part of the interface that has a NIL value is NIL

package main

import (
	"fmt"
	"strconv"
	"strings"
)

type IPAddr [4]byte
func (p IPAddr) String(a) string {
	var ipParts []string
	for _, item := range p {
		ipParts = append(ipParts, strconv.Itoa(int(item)))
	}
	return strings.Join(ipParts, ".")}func stringerTest(a) {
	hosts := map[string]IPAddr{
		"loopback":  {127.0.0.1},
		"googleDNS": {8.8.8.8}},for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
}
Copy the code

This code implements the string method for IPAddr because Printf is called with the content’s string () method to print the string.

2.1.1 Interface nesting

It is possible to embed other interfaces as anonymous fields, but the target type method set must have all methods including the embedded interface methods to implement the interface (), requiring no methods with the same name, because overloading is not supported, and not embedding itself or circular embedding because it would cause recursive errors.

package main
type stringer interface{
    string(a)string
}
type tester interface{
    stringer 
    test()
}

type data struct{}

func (*data) test(a){}

func (data) string(a) string{
    return ""
}
func main(a){
    var d data
    var t tester=&d
    t.test()
    println(t.string()}Copy the code

The superinterface variables in the above example can be implicitly converted to subset interface variables and not vice versa.

Add to the above codefunc pp(a stringer){
    println(a.string} mainfunc main(a){
    var d data
    var t tester=&d
    pp(t) // Superinterface variables are implicitly converted to subset interface variables
    var s stringer=t// The superset is converted to a subset
    println(s.string())
    //var t2 tester=s
}
Copy the code

2.2 interface value

Interfaces are also values. The default is nil. They can be passed like any other value. The interface value can be used as a parameter or return value of a function. Internally, an interface value can be thought of as a tuple containing a value and a concrete type: (value, type)

  • Interface values hold a specific value for a specific underlying type.

  • When an interface value calls a method, it executes a method of the same name of its underlying type.

An interface variable can be a variable defined as a receiver that implements its methods

package main

import (
	"fmt"
	"math"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M(a) {
	fmt.Println(t.S)
}

type F float64

func (f F) M(a) {
	fmt.Println(f)
}

func main(a) {
	var i I
        // Since T implements the M method of interface I, we can define it this way
	i = &T{"Hello"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
//output
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793
Copy the code

Empty interface

An interface value that specifies zero methods is called a null interface:

Interface {} An empty interface can hold any type of value. (Because each type implements at least zero methods.) Similar to Object

2.3 Type Assertion

package main

import "fmt"

func main(a) {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)
        // This statement asserts that the interface value I holds the concrete type T and assigns its underlying value of type T to the variable T.
	s, ok := i.(string)
	fmt.Println(s, ok)
    
	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) / / an error (panic)
	fmt.Println(f)
}
Copy the code

Another use of type assertion is type selection

package main

import "fmt"

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T! \n", v)
	}
}

func main(a) {
	do(21)
	do("hello")
	do(true)}Copy the code

2.4 Interface Application Examples

Against 2.4.1 Stringer

Stringer, defined in the FMT package, is one of the most common interfaces.

type Stringer interface {
    String() string
}
Copy the code

Stringer is a type that can describe itself as a string. FMT packages (and many more) print values through this interface.

2.4.2 the Error

The Go program uses an error value to indicate error status.

Similar to fmt.Stringer, the error type is a built-in interface:

type error interface {
    Error() string
}
Copy the code

(Similar to fmt.stringer, the FMT package also prints values with error.)

Normally the function returns an error, and the calling code should determine if the error is nil to handle the error.

i, err := strconv.Atoi("42")
iferr ! =nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", I) \ \ the errornilWhen means success; nonnilError indicates failure.Copy the code

Error exercise: Copy the Sqrt function from the previous exercise and modify it to return Error. Sqrt should return a non-nil error value when it receives a negative number. Complex numbers are also not supported. Create a new type, Type ErrNegativeSqrt FLOAT64, and implement the func (e ErrNegativeSqrt) Error() string method with an Error value. Calling this method with ErrNegativeSqrt(-2).error () should return “Cannot Sqrt Negative Number: -2”. When you modify the Sqrt function to accept a negative number, return the ErrNegativeSqrt value.

package main

import (
	"fmt"
	"math"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error(a) string{
	return fmt.Sprint("cannot Sqrt negative number:".float64(e))
}
// Note that e is converted to float in the Sprint. Because the Sprint goes back to find a method in this type that returns a String, it gets stuck in an endless loop of calls
// Why can the following y be returned as error, because it has the error method in the error interface, which implements the error interface
Var er error = y; the value of an interface can be used to load variables that implement the interface

func Sqrt(x float64) (float64, error) {
	if x<0{
		y:=ErrNegativeSqrt(x)
		return 0,y
	}
	return math.Sqrt(x),nil
}

func main(a) {
	fmt.Println(Sqrt(2))	
	fmt.Println(Sqrt(2 -))}Copy the code

2.4.3 Reader

The IO package specifies the IO.Reader interface, which represents reading from the end of the data stream.

The Go standard library contains many implementations of this interface, including files, network connections, compression, and encryption.

The IO.Reader interface has a Read method:

func (T) Read(b []byte) (n int, err error)
Copy the code

Read fills the given byte slice with data and returns the number of bytes filled and an error value. It returns an IO.eof error when it encounters the end of the data stream.

The sample code creates a strings.Reader and reads its output at a rate of 8 bytes at a time.

package main

import (
	"fmt"
	"io"
	"strings"
)

func main(a) {
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte.8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break}}}Copy the code

A common pattern of rot13Reader is that one IO.Reader wraps another IO.Reader and modifies its data flow in some way.

For example, the gzip.NewReader function takes an IO.Reader (compressed data stream) and returns a *gzip.Reader (uncompressed data stream) that also implements IO.

Write a ROT13 Reader that implements IO.Reader and reads data from another IO.Reader, and modify the data stream by applying rot13 substitution cipher.

The rot13Reader type is already available. Implement the Read method to satisfy IO.Reader.

package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}
func (ro13 *rot13Reader) Read(s []byte)(int,error){
        // I have a confused shop. Why not &ro13
	n,er:=ro13.r.Read(s)
	ifer! =nil{
		return 0,er
	}
	for i:=range s{
		s[i]=transferRot13(s[i])
	}
	return n,nil
}
func transferRot13(b byte)  byte{
	switch {
	case 'A' <= b && b <= 'M':
		b = b + 13
	case 'M' < b && b <= 'Z':
		b = b - 13
	case 'a' <= b && b <= 'm':
		b = b + 13
	case 'm' < b && b <= 'z':
		b = b - 13
	}
	return b
}

func main(a) {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}
Copy the code

2.4.4 image

The image package defines the image interface:

package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}
Copy the code

Note: the Rectangle returned by the Bounds method is actually an image.Rectangle declared in the image package.

The color. color and color.Model types are also interfaces, but are often overlooked because they use the predefined implementations image.rgba and image.rgbamodel directly. These interfaces and types are defined by the Image /color package.


Exercise: Images

Define your own Image type, implement the necessary methods and call pic.ShowImage.

The Bounds should return an image.Rectangle, such as image.Rect(0, 0, w, h).

ColorModel should return color.rgbamodel.

At should return a color. The value v of the previous image generator corresponds to color.RGBA{v, v, 255, 255}

package main

import (
	"golang.org/x/tour/pic"
	"image"
	"image/color"
)

type Image struct{}  // Create an Image structure

func (i Image) ColorModel(a) color.Model{  // Implement the color mode in the Image package
	return color.RGBAModel
}

func (i Image) Bounds(a) image.Rectangle{  // Implement Image package to generate Image boundary method
	return image.Rect(0.0.200.200)}func (i Image) At(x,y int) color.Color {  // Implement the Image package to generate a point in the Image method
	return color.RGBA{R: uint8(x), G: uint8(y), B: uint8(255), A: uint8(255)}}func ImageTest(a) {
	m := Image{}
	pic.ShowImage(m)  / / call
}
Copy the code

3. Goroutine threads

Go programs are lightweight threads managed by the GO runtime that run in the same address space and therefore must be synchronized when accessing shared memory. Go program accuracy is neither thread nor coroutine, but a combination of the two, which can maximize execution efficiency and give play to multi-core processing capability. Goroutine is similar to defer in that it has delayed execution

var c int
func counter(a) int{
    c++
    return c
}
func main(a){
    d:=100
    go func(x,y int){
        time.sleep(time.Second)
        println("go",x,y)
    }(a,counter())// Calculate and copy the parameters immediately
    a+=100
    println("main",a,counter())
    time.Sleep(time.Second*3)// Wait for the goroutine to end
}
output
main:200 2
go:100 1
Copy the code

And you can see that even though the order and definition of the go procedure are not always the same it might be delayed.

3.1 the channel channel

Channels are pipes with types through which you can send or receive values with the channel operator <-. The function of the channel is to give communication between different GO programs

ch <- v    // Send v to channel CH.
v := <-ch  // Accept the value from ch and assign v.
Copy the code

Like maps and slicing, channels must be created before use:

ch := make(chan int)
Copy the code

By default, both send and receive operations block until the other end is ready. This allows the Go program to synchronize without explicit locks or race variables. Channels can be buffered. Initialize a buffered channel by supplying the buffer length as the second argument to make:

Ch := make(chan int, 100) block if the channel buffer is full. When the buffer is empty, the receiver blocks.

The built-in functions cap() and len () return the buffer size of the channel and the number currently buffered. In addition, the channel variable itself is a pointer that can be used to determine whether it is the same object or nil by the equality operator

func main(a){
    var a,b chan int=make(chan int.3),make(chan int)
    var c chan bool
    println(a==b)
    println(a==nil)
    fmt.Prinf("%p.%D\n",a,unsafe.Sizeof(a))
}
//output
flase
true 
0x........... .8
Copy the code

The channel is divided into synchronous channel and asynchronous channel. In synchronous channel, the sender needs to know the status of the receiver, so the result obtained by using the built-in functions cap() and len() is 0,0. Asynchronous channel senders send, not receive, so they usually have buffers.

3.1.2 range and close

The sender can use close to indicate that the channel is closed and there is no value to send again. The receiver can test whether the channel is closed by assigning a second parameter to the receive expression: if no value can be received and the channel is closed, the execution is complete

v, ok := <-ch
Copy the code

Ok will then be set to false.

The loop for I := range C continues to receive values from the channel until it is turned off.

Note: Only the sender can close the channel, not the receiver. Sending data to a closed channel can cause a panic.

Also note that channels, unlike files, usually do not need to be closed. Closing is necessary only if the receiver must be told that there are no more values to send, such as terminating a range loop.

package main

import (
	"fmt"
)

func fibonacci(n int, c chan int) {
	x, y := 0.1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}

func main(a) {
	c := make(chan int.10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}
Copy the code

Close in channel sending and receiving

In addition to the simple send and receive operators, data can be processed using ok-item or Ranege patterns, but these two modes require channel closure, as shown in the example

func main(a){
    done:=make(chan struct{})
    c:=make(chan int)
    go func(a){
        defer close(done)
        for{           // The for loop continues until the channel is closed
            x,ok:=-<c  // This is the ok-item format
            ifok! {return // Determine whether the channel is closed
            }
            println(x)
        }
    }()
    
    c<- 1
    c<2 -
    c<- 3
    close(c)
    <-done
}
Copy the code

For loop receiving data, the Range mode is more concise

func main(a){
    done:make(chan struct{})
    c:=make(chan int)
    go func(a){
        defer close(done)
        for x:=range c{       // Loop receives the message until the channel is closed
            println(x)
        }
    }()
    
    c<- 1
    c<2 -
    c<- 3
    close(c)
    <-done
}
Copy the code

Practice on close

Simulate the state of a runner in a race

package main

import (
	"sync"
	"time"
)

func closeTest(a)  {
	var wg sync.WaitGroup
	ready:=make(chan struct{})
	for i:=0; i<3; i++{ wg.Add(1)
		go func(id int) {
			defer wg.Done()
			println(id,":ready")
			<-ready         // Wait for a message to enter the channel. If there is no message, the channel is blocked, or the channel is closed to prevent blocking
			println(id,"running...")
		}(i)
	}
	time.Sleep(time.Second)
	println("Ready GO!")
	close(ready)
	wg.Wait()
}

Copy the code

If they don’t close the channel, the runner will be stuck at the starting line! This is a kind of group notification of channel

Finally, we know a few rules

  • Sending data from a closed channel causes panic
  • Receives data from a closed channel, returns buffered data or a value of 0
  • The nil channel clogs up whether you send or receive
  • Repeat closing, or closing a nil channel can cause panic errors

3.1.3 a select statement

The SELECT statement enables a Go program to wait for multiple communication operations.

Select blocks until a branch can continue, at which point the branch is executed. When multiple branches are ready, one is randomly selected for execution. The default statement is executed when all channels are blocked

package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0.1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return}}}func main(a) {
	c := make(chan int)
	quit := make(chan int)
	go func(a) {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}
Copy the code
package main

import (
	"fmt"
	"time"
)

func main(a) {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println(".")
			time.Sleep(50 * time.Millisecond)
		}
	}
}
Copy the code

3.1.4 Resource Leakage

A channel may cause a Goroutine leak. Specifically, a goroutine is stuck in a sending or receiving state but never wakes up. The garbage collector will not collect the resource, causing it to sleep in the queue for a long time and leak the resource.

package main

import (
	"runtime"
	"time"
)

func leakTest(a){
	c:=make(chan int)
	for i:=0; i<10; i++{go func(a) {<-c}()
	}
}
func leakTest1(a){
	leakTest()
	for  {
		time.Sleep(time.Second)
		runtime.GC()// Enforce garbage collection}}Copy the code

The code above is blocked and never wakes up.

3.2 the lock

The lock mechanism is provided in Sync, the go standard library, and is usually provided with the Mutex data structure. Mutex has two methods: lock and unlock. You can also defer to ensure that the unlock is guaranteed.

package main

import (
	"fmt"
	"sync"
	"time"
)

// Concurrent use of SafeCounter is safe.
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc increments the value of the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mux.Lock()
	// Only one goroutine can access c.v. at a time after Lock
	c.v[key]++
	c.mux.Unlock()
}

// Value returns the current Value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Only one goroutine can access c.v. at a time after Lock
	defer c.mux.Unlock()
	return c.v[key]
}

func main(a) {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))}Copy the code

The lock must be used with a pointer as receiver, otherwise the lock will be invalidated due to replication

package main

import (
	"fmt"
	"sync"
)

type MyData struct {
	mux sync.Mutex
	num int
}
// Receiver should be *MyData
func (md MyData) plusnmber(number int) {
	defer md.mux.Unlock()
	md.mux.Lock()
	md.num+=number
	fmt.Println(md.num)
}
func anonymousMutex(a){
	var wg sync.WaitGroup
	wg.Add(4)
	var md MyData
	go func(a) {
		defer wg.Done()
		md.plusnmber(1)
	}()
	go func(a) {
		defer wg.Done()
		md.plusnmber(2)
	}()
	go func(a) {
		defer wg.Done()
		md.plusnmber(3)
	}()
	go func(a) {
		defer wg.Done()
		md.plusnmber(4)
	}()
	wg.Wait()
}

//output
4
1
2
3
Copy the code

It was found that there was no cumulative effect at all, as everyone took the lock and did their own addition almost at the same time

  • Mutex does not support recursion and will also sound a deadlock under the same Goroutine
  • You should avoid using defer Unlock when you have performance requirements
  • For single data read and write protection, try atomic operations
  • RWMux is better for concurrent reads and writes

3.2.1 Waiting for the Concurrency to End

The process exits without waiting for the end of the concurrent task. If the concurrent task is not completed at this time, the process can cause many errors. There are two ways, channel blocking and Waitgroup.

Service channel blockage

func main(a){
    exit:=make(chan struct{})// Create channel, because it is only notification, data is meaningless
    go func(a){
        time.Sleep(time.Second)
        println("goroutine done")
        close(exit)// Close the channel
    }
    println("main...")
    <-exit// If the channel is closed, unblock immediately, but if the channel is not closed, block until the go process is finished
    println("main exit")}Copy the code

sync.WaitGroup

By setting the counter so that each Goroutine is decrement until it hits zero, the block is unblocked. Let’s use the example on Gotour to understand its use exercise: Web crawler in this exercise, we will parallelize a Web crawler using the concurrency feature of Go.

Modify the Crawl function to Crawl urls in parallel without repeating them.

Tip: You can use a map to cache fetched urls, but be aware that the map itself is not concurrency safe!

  • The Done() method is used to subtract a count and is often used in the Go procedure, often with defer
  • The Add() method increments the count
  • Wait() blocks the process from exiting prematurely, waiting for all concurrent tasks to complete
package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch returns the body content of the URL and puts the URL found on the page into a slice.
	Fetch(url string) (body string, urls []string, err error)
}
// To avoid url duplication, create your own structure variable and lock it
type urlMap struct {
	urlRoute map[string] int
	mux sync.Mutex
}

func check(um urlMap,urlName string) bool{
	um.mux.Lock()
	defer um.mux.Unlock()
	_,ok:=um.urlRoute[urlName]
	if ok==false{
		um.urlRoute[urlName]=1
		return true
	}
	return false
}

// Crawl Uses fetcher to recursively Crawl the page from a URL until the maximum depth is reached.
func Crawl(url string, depth int, fetcher Fetcher,wg *sync.WaitGroup,um urlMap) {
	defer wg.Done()
	// TODO:Parallel fetching of urls.
	// TODO:Do not repeat the page fetching.
	// The following does not implement the above two cases:
	if depth <= 0 {
		return
	}
	body, urls, err := fetcher.Fetch(url)
	iferr ! =nil {
		fmt.Println(err)
		return

	}
	fmt.Printf("found: %s %q\n", url, body)
	for _, u := range urls {
		if check(um,u)==true{
			wg.Add(1)
			go Crawl(u, depth- 1, fetcher,wg,um)
		}
		continue
	}
	return
}

func CrawlTest(a) {
	var wg sync.WaitGroup
	um:=urlMap{urlRoute: make(map[string]int)}
	wg.Add(1)
	go Crawl("https://golang.org/".4, fetcher,&wg,um)
	wg.Wait()
}

// fakeFetcher is a Fetcher that returns several results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

func (f fakeFetcher) Fetch(url string) (stringAnd []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "".nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language"And []string{
			"https://golang.org/pkg/"."https://golang.org/cmd/",}},"https://golang.org/pkg/": &fakeResult{
		"Packages"And []string{
			"https://golang.org/"."https://golang.org/cmd/"."https://golang.org/pkg/fmt/"."https://golang.org/pkg/os/",}},"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt"And []string{
			"https://golang.org/"."https://golang.org/pkg/",}},"https://golang.org/pkg/os/": &fakeResult{
		"Package os"And []string{
			"https://golang.org/"."https://golang.org/pkg/",,}}}Copy the code

Unlike threads, goroutine tasks cannot be prioritized, cannot obtain numbers, have no local storage, and sometimes even return values are discarded, but other functions are easily implemented. My local storage is implemented using a structure that contains a map. In order to implement local storage, it needs to be synchronized and run concurrently to check for read and write.

3.3 Small tip in runtime package

  • Runtime. GOMAXPROCS(n int): A number of threads are created at runtime, but only a limited number of threads are performing concurrent tasks at any one time. By default, this number is equal to the number of processor cores. If n<=1, set the number of threads to the number of cores without any change.
  • Runtime.gosched (): pauses to release the thread to other tasks. The current task is put back to the queue and automatically resumes execution
  • Runtime.goexit (): Terminates the current task immediately, terminating the entire call stack. Those that have been pushed on the stack will execute, as will the package defer statement, as opposed to returning just exiting the current function. The standard library OS.exit can terminate a process, but does not perform a delayed call.

This function does not affect other concurrent tasks, causing panic and failure to catch

package main

import "runtime"

func GoExitTest(a){
	exit:=make(chan struct{})
	go func(a) {
		defer close(exit)
		defer println("a")
		func(a){
			defer func(a) {
				println("b".recover() = =nil)// add a judgment to check whether the return is normal} ()func(a) {
			println("c")
			runtime.Goexit()  // This sentence will not be run at all
			println("c done.")
			}()

			println("b done")
		}()
		println("a done")
	}()
	// If the main thread ends directly, the go process may not be executed
	//<-exit blocks until there is data in the channel or the channel is closed
	<-exit
	println("main exit")}//output
c
b
a
main exit
Copy the code

But in the main function of the main package, you not only terminate the entire call stack, you crash the process directly

func main(a){
	for i:=0; i<2; i++{go func(x int) {
			for n:=0; n<2; n++{ fmt.Printf("%c:%d\n",x,n)
				time.Sleep(time.Millisecond)
			}
		}(i)
	}
	runtime.Goexit()
	println("main,exit.")}Copy the code

That means the rest of the main function doesn’t work

3.4 panic and recover

The built-in panic function stops the normal execution of the current goroutine. When function F calls Panic, the normal execution of function F is immediately stopped, then all functions defer in F are run, and F returns to the function that called it. For caller G, function F behaves just like panic, Terminate G and run the function defer in G, which continues to all the functions in Goroutine. Panic can be caught with the built-in RECOVER

Similar to goexit, which terminates the call stack, panic is an interface and Recover is an interface in the source code.

The recover built-in function is used to manage goroutines that have panic behavior. Recover runs in the defer function, taking error values thrown by Panic and restoring the program to its normal execution state. If recover is called outside of the defer function, recover will not stop and catch panic errors if there is no panic in goroutine or if the value of panic captured is nil, recover will return nil. The return value of RECOVER indicates whether the current goroutine has panic behavior.

Panic’s built-in functions pass in an empty interface value, meaning any type can be passed in

package main

import "fmt"

func outside(a){
	defer fmt.Println("inside funciton done")
	fmt.Println("start inside function")
	inside()
	fmt.Println("test the rest of the ouside")}func inside(a){
	defer func(a) {
		fmt.Println("defer done")
	}()
	panic(struct {}{})
}
func main(a){
    outside()
    fmt.Println("main done")}//output
start inside function
defer done
inside funciton done
panic: (struct {}) 0xa37350
goroutine 1 [running]:
main.inside()
        C:/Users/I546894/Desktop/golearning/panicTest.go:15 +0x4a
main.outside()
        C:/Users/I546894/Desktop/golearning/panicTest.go:8 +0xd1
main.main()
        C:/Users/I546894/Desktop/golearning/main.go:81 +0x2db
exit status 2
Copy the code

Through the example above we can notice the panic call defer is pressed into the function of the call stack, but won’t run the program, and then will return to the caller, defer the pressure in the caller to call stack section, still does not perform the rest, finally at the top of an error function, can see the content of panic. So what happens when YOU add Recover? By convention, recover is used in a derfer statement, but cannot be called directly.

package main

import (
	"fmt"
)

func outside(a){
	defer fmt.Println("inside funciton done")
	fmt.Println("start inside function")
	inside()
	fmt.Println("test the rest of the ouside")}func inside(a){
	defer func(a) {
		fmt.Println("defer done")
	}()
	defer func(a) {
		// Recover () has a better way to call it
		if pan:=recover(a); pan! =nil {
			fmt.Println(pan)
		}
	}()
	panic(struct {}{})
	fmt.Println("the rest of the inside")}//output
start inside function
{}
defer done
test the rest of the ouside
inside funciton done
main done
Copy the code

We can find that after Recover caught panic, it cut the function directly, and the Goroutine where Recover resides takes over the running of the function, so that the line “the rest fo the inside” cannot be printed, and then return to the caller function to continue normal operation. Does that remind you of something? Is to try… catch! So can recover be put outside the ouside function, the caller?

package main

import (
	"fmt"
)

func outside(a){
	defer func(a) {
		recover(a)// There is a better way to call it
		//if pan:=recover(); pan! =nil {
		//	fmt.Println(pan)
		/ /}
		fmt.Println("recover goroutine")
	}()
	defer fmt.Println("inside funciton done")
	fmt.Println("start inside function")
	inside()
	fmt.Println("test the rest of the ouside")}func inside(a){
	defer func(a) {
		fmt.Println("defer done")
	}()
	//defer func() {
	// recover()// There is a better way to call it
	// //if pan:=recover(); pan! =nil {
	//	//	fmt.Println(pan)
	/ / / /}
	//	fmt.Println("recover goroutine")
	/ /} ()
	panic(struct {}{})
	fmt.Println("the rest of the inside")}//output
start inside function
defer done
inside funciton done
recover goroutine
main done
Copy the code

The answer is yes!