This article is long, it is recommended to click the original article at the end of the article, after the collection to view. Alternatively, connect the collector PC to ๐Ÿ”— tigerb.cn/php2go/

Copyright statement

  • Distribution of this Manual and materially modified versions is prohibited without the express authorization of the copyright holder.
  • Distribution of this work and its derivative works in standard (paper) book form is prohibited without prior authorization from the copyright owner.
  • No cooperation in any form with any third party.

preface

A brief manual is compiled to help you get started with Go language efficiently. It is mainly to strengthen your understanding by comparing the differences between PHP and Go. The content is mainly divided into the following four parts:

  • Linguistic level difference
  • Basic grammatical differences
  • Avoid pit guide
  • Use the advanced

Linguistic level difference

Note: The following is based on the mainstream PHP-FPM mode of PHP.Copy the code
Compare the item PHP Go
String representation Single quotation marks (PSR) Double quotation marks
Concatenated string . +
Language version compatibility bad Backwards compatible
Code style. There are no official standards, and community standards start late Official standards and tools have been provided since the beginning
Scripting language is not
Strongly typed language No (PHP7 supports strict mode) is
Whether garbage collection is supported is is
Object-oriented Language (OOP) Spirit likeness Partially supported, the core is composite reuse
Whether inheritance is supported is No (with composite reuse)
Whether to support interfaces is is
Whether to support try… catch… is no
Whether to support package management is is
Cross-platform support is is
Environmental construction cost high low
Implement way Cli mode, PHP-fpm mode (โ‘ ) binary
Process model Multiple processes Single process
Native Whether to create TCP/UDP services Yes (poor support, production unavailable) is
Whether to create the HTTP service Yes (poor support, production unavailable) is
Process blocking is no
Whether coroutines are supported No (2) is
Concurrency capability (โ‘ข) weak strong
Whether to run in resident memory Not (4) is
Import file mode requireorincludeCorresponding to the file importImport packages
Whether to support unit testing is is
Whether benchmark is supported no is
Whether to support performance analysis Support (xhprof/tideways) Support (pprof/DLV)
Cost of using performance analysis tools High (high installation and expansion cost) Very low
Swoole coroutine (SWOole coroutine) is one of the most popular modes in the world. It is also possible to use THE SWOole coroutine framework (SWOole coroutine framework) in the WORLD. It is also possible to use THE SWOole coroutine framework (SWOole coroutine framework) in the worldCopy the code

In the initial transition from PHP to Go, the focus was on a shift in programming consciousness, especially the following:

  • Strongly typed
  • Resident memory operation
  • Understand and use Pointers
  • Concurrent security
  • Timely release or return of resources

Basic grammatical differences

Note: The following is based on PHP5.4+Copy the code

Common basic type comparisons

PHP has few and simple types. Common data types in PHP include Boolean, string, int, float, array, and object.

PHP’s common data types are compared with their Go counterparts or similar types.

Language \ type boolean string int float array object
PHP bool string int float Array (1, 2, 3) index array, the array (‘ 1 ‘= > 1,’ 2 ‘= > 2,’ 3 ‘= > 3) associative array Instantiate class
Go bool string Int, int8, INT16, INT32, INT64, uint, uint8, uint16, uint32, uint64 Float32, float64 [length]type More like struct

In addition, Go supports a much richer variety of types:

type
Slice slices (PHP equivalent to indexed arrays)
Map (equivalent to associative arrays in PHP)
Channel (channel, shared by communication, don’t communicate by sharing)
Pointer (Go has a pointer type for each value type)
Byte (corresponds to uint8 alias, which can represent Ascaii code)
Rune (corresponding to Int32, which can represent Unicode)
, etc.
A custom type, for exampletype userDefinedType int32
## Comparison of common initialization methods for basic types
type PHP Go(define variable bandvarKeywords, or without using grammar sugar directly: =)
boolean $varStr = true; var varStr bool = true

orvar varStr = true

orvarStr := true
string $varStr = 'demo'; var varStr string = ""

orvarStr := ""(:= Writing method is omitted below)
int32 $varNum = 0; var varInt32 int32 = 0
int64 Same as above var varInt64 int64 = 0
float32 $varNum = 0.01; var varFloat32 float32 = 0
float64 Same as above var varFloat64 float64 = 0
array $varArray = array();

Or grammatical sugar$varArray = [];
var varArray [6]int32 = [6]int32{}
Slice (slice) Again, PHP is called index data var varSlice []int32 = []int32{}Slices are automatically expanded relative to the data
map $varMap = array('key' => 'value'); var varMap map[string]int32 = map[string]int32{}
A closure (closures) $varClosure = function() {}; var varClosure func() = func() {}
channel There is no var varChannel chan string = make(chan string)No cache channel;

var varChannelBuffer chan string = make(chan string, 6)Have a cache channel

Instantiation of a PHP class versus initialization of a Go structure

Instantiation of the PHP class

/* Define class */
class ClassDemo {
    // Private property
    private $privateVar = "";
    // Public property
    public $publicVar = "";
    // The constructor
    public function __construct()
    {
        // Execute when the class is instantiated
    }
    // Private method
    private function privateFun()
    {}// Public method
    public function publicFun()
    {}}// Instantiate class ClassDemo to obtain ClassDemo objects
$varObject = new ClassDemo(); // Object (class)
Copy the code

Initialization of the Go structure

// When the package is initialized
func init(a){}type StructDemo struct{
    // The lower case hump indicates private property
    // Cannot be exported
    privateVar string
    // The uppercase hump indicates the public property
    / / can be exported
    PublicVar string
}

// A lowercase hump indicates a private method
// Private method of StructDemo
func (demo *StructDemo) privateFun(a) error {
    return nil
}

// The uppercase hump indicates the public property
// Public method of StructDemo
func (demo *StructDemo) PublicFun(a) error {
    return nil
}

// Initialize StructDemo
// structDemo := &StructDemo{}
Copy the code

Comparison of common functions

Description of common functions PHP Go
The length of the array count() len()
Split a string into an array explode() strings.Split(s string, sep string) []string
Turn the capital strtoupper() strings.ToUpper(s string) string
Turn to lowercase strtolower() strings.ToLower(s string) string
Remove the blank space trim() strings.Trim(s, cutset string) string
Json serialization json_encode() json.Marshal(v interface{}) ([]byte, error)
Json deserialization json_decode() json.Unmarshal(data []byte, v interface{}) error
Serialization (no longer recommended) Serialize () and unserialize () packageGithub.com/wulijun/go-…
md5 md5() Package crypto/md5
Terminal output Echo, var_dump, etc fmt.Println(a … interface{})
Various types are interchanged Intval (), etc Package strconv

Avoid pit guide

  1. Use globals with caution. They are not destroyed after completing a request, as they are in PHP
  2. Parameter isslice,mapType. Note that the value can be modified globally
  3. When resources are used up, release or recycle them
  4. Do not rely on the map traversal order
  5. Do not write maps concurrently
  6. Note That the pointer type is not nullnilAnd then the operation
  7. The Go language does not support inheritance, but it does have synthetic reuse

1. Use global variables with caution. Global variables are not destroyed after completing a request, as they are in PHP

package main

import (
    "sync/atomic"

    "github.com/gin-gonic/gin"
)

// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0

// Simulate the interface logic
func main(a) {
    r := gin.Default()
    r.GET("/ping".func(c *gin.Context) {
        // Atom plus one
        atomic.AddInt32(&GlobalVarDemo, 1)
        c.JSON(200, gin.H{
            "message": GlobalVarDemo,
        })
    })
    r.Run()
}

// When we request the interface many times, it is obvious that global variables are not destroyed after completing a request, as in PHP.
// However, in PHP, global variables are automatically destroyed after completing a request.
/ / curl "127.0.0.1:8080 / ping"
// {"message":1}                                                                     
/ / curl "127.0.0.1:8080 / ping"
// {"message":2} <------- value incrementing
/ / curl "127.0.0.1:8080 / ping"
// {"message":3} <------- value incrementing
Copy the code

2. The parameter isslice,mapType. Note that the value can be modified globally

Like PHP passing by reference, Go is all about passing values, for reasons described below.

/ / section
package main

import "fmt"

func main(a) {
	paramDemo := []int32{1}
	fmt.Printf("main.paramDemo 1 %v, pointer: %p \n", paramDemo, &paramDemo)
	/ / shallow copy
	demo(paramDemo)
	fmt.Printf("main.paramDemo 2 %v, pointer: %p \n", paramDemo, &paramDemo)
}

func demo(paramDemo []int32) ([]int32, error) {
	fmt.Printf("main.demo.paramDemo pointer: %p \n", &paramDemo)
	paramDemo[0] = 2
	return paramDemo, nil
}

// main.paramDemo 1 [1], pointer: 0xc00000c048
// main.demo.paramDemo Pointer: 0xC00000C078 <------- Value copy occurred because memory addresses were different
// main.paramDemo 2 [2] <------- The original value is modified

// main.paramDemo 1 [1], pointer: 0xc0000a6030
// main.demo.paramDemo Pointer: 0xC0000A6060 <------- Value copy occurred because memory addresses were different
// main.paramDemo 2 [2], pointer: 0xc0000A6030 <------- The original value was modified



// the =========== array does not have this problem ===========
package main

import "fmt"

func main(a) {
	paramDemo := [1]int32{1}
	fmt.Println("main.paramDemo 1", paramDemo)
	demo(paramDemo)
	fmt.Println("main.paramDemo 2", paramDemo)
}

func demo(paramDemo [1]int32) ([1]int32, error) {
	paramDemo[0] = 2
	return paramDemo, nil
}

// [Running] go run "... /demo/main.go"
// main.paramDemo 1 [1]
// main.paramDemo 2 [1] < the value ------- has not been modified

//===========Map has the same problem ===========

package main

import "fmt"

func main(a) {
	paramDemo := map[string]string{
		"a": "a",
	}
	fmt.Println("main.paramDemo 1", paramDemo)
	demo(paramDemo)
	fmt.Println("main.paramDemo 2", paramDemo)
}

func demo(paramDemo map[string]string) (map[string]string, error) {
	paramDemo["a"] = "b"
	return paramDemo, nil
}

// [Running] go run "... /demo/main.go"
// main.paramDemo 1 map[a:a]
// main.paramDemo 2 map[a:b] <------- the value is modified

Copy the code

Why is that?

A: Go is all value passing, shallow copy, slice and Map's underlying type is a structure, and the type that actually stores the value is a pointer.Copy the code
/ / versions / 1.13.8 / SRC/runtime/slice. Go
// Slice source structure
type slice struct {
    array unsafe.Pointer // The type of the actual stored value is a pointer
    len   int
    cap   int
}

/ / versions / 1.13.8 / SRC/runtime/map. Go
// Map source structure
type hmap struct {
    count     int
    flags     uint8
    B         uint8
    noverflow uint16
    hash0     uint32

    buckets    unsafe.Pointer // The type of the actual stored value is a pointer
    oldbuckets unsafe.Pointer
    nevacuate  uintptr  

    extra *mapextra
}
Copy the code

How to do?

A: A deep copy is to create a new piece of memory, pointer to the new memory address, and copy the original value. As follows:Copy the code
package main

import "fmt"

func main(a) {
	paramDemo := []int32{1}
	fmt.Println("main.paramDemo 1", paramDemo)
	// Initialize the new space
	paramDemoCopy := make([]int32.len(paramDemo))
	/ / copy
	copy(paramDemoCopy, paramDemo)
	demo(paramDemoCopy)
	fmt.Println("main.paramDemo 2", paramDemo)
}

func demo(paramDemo []int32) ([]int32, error) {
	paramDemo[0] = 2
	return paramDemo, nil
}

// [Running] go run "... /demo/main.go"
// main.paramDemo 1 [1]
// main.paramDemo 2 [1]

Copy the code

3. Release or recycle resources when resources are used up

package main

import (
	"github.com/gomodule/redigo/redis"
)

var RedisPool *redis.Pool

func init(a) {
	RedisPool = NewRedisPool()
}

func main(a) {
	redisConn := RedisPool.Get()
	// Remember to defer releasing the resource
	defer redisConn.Close()
}

func NewRedisPool(a) *redis.Pool {
	/ / a little...
	return &redis.Pool{}
}

Copy the code

Why is that?

A: Avoid resources being held inefficiently, wasting resources and increasing the number of connections to resources. Second, returning the connection pool also reduces the cost of creating new resources.Copy the code
  • The number of resource connections increases linearly
  • If it is always held, the resource server also has a timeout period

4. Do not rely on the map traversal order

Previously, PHP’s “Map” (associative array) had a stable order of elements no matter how many times it was traversed, as follows:


      

$demoMap = array(
    'a'= >'a'.'b'= >'b'.'c'= >'c'.'d'= >'d'.'e'= >'e',);foreach ($demoMap as $v) {
    var_dump("v {$v}");
}

// First execution
[Running] php "... /php/demo.php"
string(3) "v a"
string(3) "v b"
string(3) "v c"
string(3) "v d"
string(3) "v e"

// The NTH execution
// The sequence of traversal results is stable
[Running] php "... /php/demo.php"
string(3) "v a"
string(3) "v b"
string(3) "v c"
string(3) "v d"
string(3) "v e"
Copy the code

But in The Go language, it’s a different story.

package main

import "fmt"

func main(a) {
	var demoMap map[string]string = map[string]string{
		"a": "a"."b": "b"."c": "c"."d": "d"."e": "e",}for _, v := range demoMap {
		fmt.Println("v", v)
	}
}

// First execution
// [Running] go run "... /demo/main.go"
// v a
// v b
// v c
// v d
// v e

// Execute the second time
// The sequence of elements has changed as a result of the traverse
// [Running] go run "... /demo/main.go"
// v e
// v a
// v b
// v c
// v d
Copy the code

Why is that?

Answer: The underlying implementation is array + similar to the zipper method. The hash function is written out of order. 2. Multiples and 3. PHP then maintains the order of map elements through the extra memory space.Copy the code

5. Do not write maps concurrently

package main

import (
	"testing"
)

func BenchmarkDemo(b *testing.B) {
	var demoMap map[string]string = map[string]string{
		"a": "a"."b": "b",}// Simulate concurrent writing to map
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			demoMap["a"] = "aa"}})}// BenchmarkDemo
// fatal error: concurrent map writes
// fatal error: concurrent map writes
Copy the code

Why is that?

A: The concurrency is unsafe and triggers panic: FATAL error: concurrent map writes.Copy the code
Go version 1.13.8 source code
// The hashWriting value is 4
ifh.flags&hashWriting ! =0 {
	throw("concurrent map read and map write")}Copy the code

6. Ensure that the pointer type is not nullnilAnd then the operation

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main(a) {
	resp, err := http.Get("https://www.example.com")
	ifresp.StatusCode ! = http.StatusOK || err ! =nil {
		// Panic will be triggered when resp is nil
		/ / when resp. StatusCode! = Http. StatusOK When err is nil, panic is triggered
		log.Printf("err: %s", err.Error())
	}
}


// [Running] go run "... /demo/main.go"
// panic: runtime error: invalid memory address or nil pointer dereference

Copy the code
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main(a) {
	// Simulate the request business code
	resp, err := http.Get("https://www.example.com")
	fmt.Println(resp, err)
	iferr ! =nil {
		// Report an error and log the exception
		log.Printf("err: %s", err.Error())
		return
	}
	// The simulated service code is not the successful code
	ifresp ! =nil&& resp.StatusCode ! = http.StatusOK {// Report an error and log the exception}}Copy the code

7. Go does not support inheritance, but there is synthetic reuse

abstract class AbstractClassDemo {

    // Abstract method
    abstract public function demoFun();
    
    // Public method
    public function publicFun()
    {
        $this->demoFun(); }}class ClassDemo extends AbstractClassDemo {

    public function demoFun()
    {
        var_dump("Demo"); }} (new ClassDemo())->demoFun();

// [Running] php "... /php/demo.php"
// string(4) "Demo"
Copy the code
package main

import (
	"fmt"
)

// Infrastructure
type Base struct{}/ / Base DemoFun
func (b *Base) DemoFun(a) {
	fmt.Println("Base")}func (b *Base) PublicFun(a) {
	b.DemoFun()
}

type Demo struct {
	// Composite reuse Base
	Base
}

/ / the Demo DemoFun
func (d *Demo) DemoFun(a) {
	fmt.Println("Demo")}func main(a) {
	/ / execution
	(&Demo{}).PublicFun()
}

// [Running] go run "... /demo/main.go"
// Base <------ Note that this is a method of synthesising a reused structure
Copy the code

Use the advanced

  1. Hot load tool Bee
  2. Goroutine concurrency controlsync.WaitGroupThe use of the package
  3. Child Goroutine timeout controlcontext.ContextThe use of the package
  4. A map for concurrency securitysync.MapThe use of the package
  5. Reduce GC pressuresync.PoolThe use of the package
  6. Reduce cache penetrationsingleflightThe use of the package
  7. The use of the Channel
  8. Unit testing & benchmarking
  9. Performance analysis

1. Hot loading tool Bee

Function: Run Go code in hot loading mode, monitor code changes and re-run the code to improve development efficiency.

Use:

Install go get github.com/beego/bee/v2 Start the project in hot load mode SOAAGENT=10.40.24.126 bee run-main =main. go-runargs ="start"Copy the code

2.Goroutine concurrency controlsync.WaitGroupThe use of the package

Effect: A Goroutine can wait until the execution of a child of the current Goroutine is complete.

Use:

package main

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

func main(a) {
	wg := &sync.WaitGroup{}

	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		fmt.Println("Sub A starts execution.")
		time.Sleep(5 * time.Second)
		fmt.Println("Sub A completed.")
	}(wg)

	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		fmt.Println("Child B begins execution.")
		time.Sleep(5 * time.Second)
		fmt.Println("Subb completed.")
	}(wg)

	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		fmt.Println("Subc begins execution.")
		time.Sleep(5 * time.Second)
		fmt.Println("Completion of subC")
	}(wg)

	fmt.Println("The Lord waits")
	wg.Wait()
	fmt.Println("The Lord exits.")}// First execution
// [Running] go run "... /demo/main.go"
// Sub a starts execution
// Subc starts execution
// Subb starts execution
// Main wait <------ Note that this is not the same as the location printed below, because the current code concurrent execution is not guaranteed execution order
// Subb is finished
// Suba is finished
// The subc is finished
/ / the main exit

// First execution
// [Running] go run "... /demo/main.go"
// Main wait <------ Note that this is not the same as the position printed above, because the current code concurrent execution is not guaranteed execution order
// Sub a starts execution
// Subc starts execution
// Subb starts execution
// Subb is finished
// The subc is finished
// Suba is finished
// Primary exit <------ The primary Goroutine waits until all the child goroutines have executed
Copy the code

3. Subgoroutine timeout controlcontext.ContextThe use of the package

Function: The first parameter of the Go language is usually context.Context type, 1. 2. Control the sub-goroutine timeout exit 3. Control the sub-Goroutine timed exit

Use:

package main

import (
	"context"
	"fmt"
	"time"
)

func main(a) {
	ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
	defer cancel()
	go func(ctx context.Context) {
		execResult := make(chan bool)
		// Simulate the business logic
		go func(execResult chan<- bool) {
			// Simulate processing timeouts
			time.Sleep(6 * time.Second)
			execResult <- true
		}(execResult)
		// Wait for the result
		select {
		case <-ctx.Done():
			fmt.Println("Timeout exit")
			return
		case <-execResult:
			fmt.Println("Processing completed")
			return
		}
	}(ctx)

	time.Sleep(10 * time.Second)
}

// [Running] go run "... /demo/main.go"
// Exit due to timeout
Copy the code

4. Map of concurrency securitysync.MapThe use of the package

Function: A map with concurrent security and supports concurrent write. The performance of scenarios with more read and less write is good.

Use:

package main

import (
	"sync"
	"testing"
)

func BenchmarkDemo(b *testing.B) {
	demoMap := &sync.Map{}
	demoMap.Store("a"."a")
	demoMap.Store("b"."b")
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			demoMap.Store("a"."aa")}}}// BenchmarkDemo
// Benchmarkdemo-4 6334993 203.8 ns/op 16 B/op 1 allocs/op
// PASS
/ / not panic
Copy the code

5. Reduce GC pressuresync.PoolThe use of the package

Functions: Reuse objects and reduce GC pressure.

Use:

5.1 Code example for Not Using Sync. Pool

package main

import (
	"sync"
	"testing"
)

type Country struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
type Province struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
type City struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
type County struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
type Street struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// Simulation data
// Address information object
type AddressModule struct {
	Consignee       string    `json:"consignee"`
	Email           string    `json:"email"`
	Mobile          int64     `json:"mobile"`
	Country         *Country  `json:"country"`
	Province        *Province `json:"province"`
	City            *City     `json:"city"`
	County          *County   `json:"county"`
	Street          *Street   `json:"street"`
	DetailedAddress string    `json:"detailed_address"`
	PostalCode      string    `json:"postal_code"`
	AddressID       int64     `json:"address_id"`
	IsDefault       bool      `json:"is_default"`
	Label           string    `json:"label"`
	Longitude       string    `json:"longitude"`
	Latitude        string    `json:"latitude"`
}

// Sync. Pool is not used
func BenchmarkDemo_NoPool(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			// Direct initialization
			addressModule := &AddressModule{}
			addressModule.Consignee = ""
			addressModule.Email = ""
			addressModule.Mobile = 0
			addressModule.Country = &Country{
				ID:   0,
				Name: "",
			}
			addressModule.Province = &Province{
				ID:   0,
				Name: "",
			}
			addressModule.City = &City{
				ID:   0,
				Name: "",
			}
			addressModule.County = &County{
				ID:   0,
				Name: "",
			}
			addressModule.Street = &Street{
				ID:   0,
				Name: "",
			}
			addressModule.DetailedAddress = ""
			addressModule.PostalCode = ""
			addressModule.IsDefault = false
			addressModule.Label = ""
			addressModule.Longitude = ""
			addressModule.Latitude = ""
			// The following code is meaningless just to avoid syntax errors
			if addressModule == nil {
				return}}})}// The result of not using sync.Pool
// goos: darwin
// goarch: amd64
// pkg: demo
// cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
// BenchmarkDemo_NoPool-4 144146564 84.62 ns/op 120 B/op 5 allocs/op
// PASS
// ok  	demo	21.782s

Copy the code

Perform analysis without sync.Pool: flame diagram &Top function

You can clearly see that the GC process consumes a lot of CPU.

5.2 Code Example for Using Sync. Pool

/ / use the sync. The Pool
func BenchmarkDemo_Pool(b *testing.B) {
	// Use the cache Pool sync.Pool
	demoPool := &sync.Pool{
		// Define the anonymous function that initializes the structure
		New: func(a) interface{} {
			return &AddressModule{
				Country: &Country{
					ID:   0,
					Name: "",
				},
				Province: &Province{
					ID:   0,
					Name: "",
				},
				City: &City{
					ID:   0,
					Name: "",
				},
				County: &County{
					ID:   0,
					Name: "",
				},
				Street: &Street{
					ID:   0,
					Name: "",
				},
			}
		},
	}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			// Get the object from the cache pool
			addressModule, _ := (demoPool.Get()).(*AddressModule)
			// The following code is meaningless just to avoid syntax errors
			if addressModule == nil {
				return
			}

			// Reset the object to return the object to the cache pool
			addressModule.Consignee = ""
			addressModule.Email = ""
			addressModule.Mobile = 0
			addressModule.Country.ID = 0
			addressModule.Country.Name = ""
			addressModule.Province.ID = 0
			addressModule.Province.Name = ""
			addressModule.County.ID = 0
			addressModule.County.Name = ""
			addressModule.Street.ID = 0
			addressModule.Street.Name = ""
			addressModule.DetailedAddress = ""
			addressModule.PostalCode = ""
			addressModule.IsDefault = false
			addressModule.Label = ""
			addressModule.Longitude = ""
			addressModule.Latitude = ""
			// Return object to cache pool
			demoPool.Put(addressModule)
		}
	})
}

// Use sync.Pool to execute the result
// goos: darwin
// goarch: amd64
// pkg: demo
// cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
// BenchmarkDemo_Pool-4 988550808 12.41 ns/op 0 B/op 0 allocs/op
// PASS
// ok  	demo	14.215s

Copy the code

Use sync.Pool to perform the analysis: flame diagram &Top function

Runtime. mallocgc is no longer visible in top

The use of the flame diagram and the Top function will be discussed below.Copy the code

6. Reduce cache penetrationsingleflightThe use of the package

Effect: Reduce the number of requests during cache penetration.

Use:

package main

import (
	"io/ioutil"
	"net/http"
	"sync"
	"testing"

	"golang.org/x/sync/singleflight"
)

// No code example using SingleFlight
func TestDemo_NoSingleflight(t *testing.T) {
	t.Parallel()
	wg := sync.WaitGroup{}
	// Simulate concurrent remote calls
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(a) {
			defer wg.Done()
			resp, err := http.Get("http://example.com")
			iferr ! =nil {
				t.Error(err)
				return
			}
			_, err = ioutil.ReadAll(resp.Body)
			iferr ! =nil {
				t.Error(err)
				return
			}
			t.Log("log")
		}()
	}

	wg.Wait()
}

// Example code using SingleFlight
func TestDemo_Singleflight(t *testing.T) {
	t.Parallel()
	singleGroup := singleflight.Group{}
	wg := sync.WaitGroup{}
	// Simulate concurrent remote calls
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(a) {
			defer wg.Done()
			/ / use singleflight
			res, err, shared := singleGroup.Do("cache_key".func(a) (interface{}, error) {
				resp, err := http.Get("http://example.com")
				iferr ! =nil {
					return nil, err
				}
				body, err := ioutil.ReadAll(resp.Body)
				iferr ! =nil {
					return nil, err
				}
				return body, nil
			})
			iferr ! =nil {
				t.Error(err)
				return
			}
			_, _ = res.([]byte)
			t.Log("log", shared, err)
		}()
	}

	wg.Wait()
}

Copy the code

To capture a request for domain example.com, run the tcpdump host example.com command

Three requests were made without Singleflight

Only one request was made using Singleflight

7. Use of Channel

Function: Do not communicate through shared memory. Communicate to achieve shared memory. It’s a pipe.

Use:

package main

import (
	"fmt"
	"time"
)

// Respond to a common structure
type APIBase struct {
	Code    int32  `json:"code"`
	Message string `json:"message"`
}

// Simulate the response structure of interface A
type APIDemoA struct {
	APIBase
	Data APIDemoAData `json:"data"`
}

type APIDemoAData struct {
	Title string `json:"title"`
}

// Simulate the response structure of interface B
type APIDemoB struct {
	APIBase
	Data APIDemoBData `json:"data"`
}

type APIDemoBData struct {
	SkuList []int64 `json:"sku_list"`
}

// Simulate the interface logic
func main(a) {
	// Create A channel for interface A to transmit the results
	execAResult := make(chan APIDemoA)
	// Create a channel for interface B to transmit the results
	execBResult := make(chan APIDemoB)

	// Call interface A concurrently
	go func(execAResult chan<- APIDemoA) {
		// Simulate the remote call process of interface A
		time.Sleep(2 * time.Second)
		execAResult <- APIDemoA{}
	}(execAResult)

	// Call interface B concurrently
	go func(execBResult chan<- APIDemoB) {
		// Simulate the remote call process of interface B
		time.Sleep(1 * time.Second)
		execBResult <- APIDemoB{}
	}(execBResult)

	var resultA APIDemoA
	var resultB APIDemoB
	i := 0
	for {
		if i >= 2 {
			fmt.Println("Quit")
			break
		}
		select {
		case resultA = <-execAResult: // Wait for A response from interface A
			i++
			fmt.Println("resultA", resultA)
		case resultB = <-execBResult: // Wait for the response from interface B
			i++
			fmt.Println("resultB", resultB)
		}
	}
}

// [Running] go run "... /demo/main.go"
// resultB {{0 } {[]}}
// resultA {{0 } {}}
/ / exit
Copy the code

8. Unit testing & benchmarking

Function: Debug code block and interface during development; Benchmark code blocks, interfaces, and analyze performance issues, including CPU usage, memory usage, etc. You can do a comparison test. The CI phase detects code quality and reduces bugs.

Use:

8.1 Unit Testing

A very simple example of a unit test:

package main

import (
	"io/ioutil"
	"net/http"
	"testing"
)

func TestDemo(t *testing.T) {
	t.Parallel()
	// Simulate the call interface
	resp, err := http.Get("http://example.com?user_id=121212")
	iferr ! =nil {
		t.Error(err)
		return
	}
	body, err := ioutil.ReadAll(resp.Body)
	iferr ! =nil {
		t.Error(err)
		return
	}
	t.Log("body".string(body))
}

/ / execution
// go test -timeout 30s -run ^TestDemo$ demo -v -count=1
// === RUN TestDemo
// === PAUSE TestDemo
// === CONT TestDemo
/ /...
// -- PASS: TestDemo (0.45s)
// PASS
/ / ok demo 1.130 s
Copy the code

Examples of unit tests for multiple test cases:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"testing"
)

type Req struct {
	UserID int64
}

func TestDemo(t *testing.T) {
	t.Parallel()
	tests := []struct {
		TestName string
		*Req
	}{
		{
			TestName: "Test Case 1",
			Req: &Req{
				UserID: 12121212,
			},
		},
		{
			TestName: "Test Case 2",
			Req: &Req{
				UserID: 829066,,}}}for _, v := range tests {
		t.Run(v.TestName, func(t *testing.T) {
			// Simulate the call interface
			url := fmt.Sprintf("http://example.com?user_id=%d", v.UserID)
			resp, err := http.Get(url)
			iferr ! =nil {
				t.Error(err)
				return
			}
			body, err := ioutil.ReadAll(resp.Body)
			iferr ! =nil {
				t.Error(err)
				return
			}
			t.Log("body".string(body), url)
		})
	}
}

/ / execution
// go test -timeout 30s -run ^TestDemo$ demo -v -count=1
// === RUN TestDemo
// === PAUSE TestDemo
// === CONT TestDemo
// === RUN TestDemo/ Test case 1
// ...
// === RUN TestDemo/ Test Case 2
// ...
// -- PASS: TestDemo (7.34s)
// -- PASS: TestDemo/ test case 1 (7.13s)
// -- PASS: TestDemo/ test case 2 (0.21s)
// PASS
// ok  	demo	7.984s

Copy the code

8.2 Benchmark Test

A simple benchmark:

package main

import (
	"sync"
	"testing"
)

// Stress test sync.map
func BenchmarkSyncMap(b *testing.B) {
	demoMap := &sync.Map{}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			demoMap.Store("a"."a")
			for i := 0; i < 1000; i++ {
				demoMap.Load("a")}}})}// go test -benchmem -run=^$ -bench ^(BenchmarkSyncMap)$ demo -v -count=1 -cpuprofile=cpu.profile -memprofile=mem.profile -benchtime=10s

// goos: darwin
// goarch: amd64
// pkg: demo
// BenchmarkSyncMap
// BenchmarkSyncMap-4
// 570206 23047 ns/op 16 B/op 1 allocs/op
// PASS
// ok  	demo	13.623s
Copy the code

Benchmarking:

package main

import (
	"sync"
	"testing"
)

// Stress test sync.map
func BenchmarkSyncMap(b *testing.B) {
	demoMap := &sync.Map{}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			demoMap.Store("a"."a")
			for i := 0; i < 1000; i++ {
				demoMap.Load("a")}}})}// Implement a concurrent map with read/write locks
type ConcurrentMap struct {
	value map[string]string
	mutex sync.RWMutex
}

/ / write
func (c *ConcurrentMap) Store(key string, val string) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if c.value == nil {
		c.value = map[string]string{}
	}
	c.value[key] = val
}

/ / read
func (c *ConcurrentMap) Load(key string) string {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	return c.value[key]
}

// Stress test concurrent map
func BenchmarkConcurrentMap(b *testing.B) {
	demoMap := &ConcurrentMap{}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			demoMap.Store("a"."a")
			for i := 0; i < 1000; i++ {
				demoMap.Load("a")}}})}// go test -benchmem -run=^$ -bench . demo -v -count=1 -cpuprofile=cpu.profile -memprofile=mem.profile -benchtime=10s

// goos: darwin
// goarch: amd64
// pkg: demo
// BenchmarkSyncMap
// BenchmarkSyncMap-4 668082 15818 ns/op 16 B/op 1 allocs/op
// BenchmarkConcurrentMap
// BenchmarkConcurrentMap-4 171730 67888 ns/op 0 B/op 0 allocs/op
// PASS
// coverage: 0.0% of statements
// ok  	demo	23.823s

Copy the code

9. Performance analysis

Function: CPU analysis and memory analysis. Quickly locate code problems and improve code performance by visualizing call links, visualizing fire diagrams, and TOP functions.

  • pprof
  • trace
  • dlv

Use:

9.1 Use of pprof

9.1.1 Benchmark Scenario

  1. First write benchmark cases and reuse the abovesync.MapUse cases:
package main

import (
	"sync"
	"testing"
)

// Stress test sync.map
func BenchmarkSyncMap(b *testing.B) {
	demoMap := &sync.Map{}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			demoMap.Store("a"."a")
			for i := 0; i < 1000; i++ {
				demoMap.Load("a")}}})}Copy the code
  1. Perform benchmarking and generatecpu.profileFiles andmem.profile File. The command is

Go test-run =^ โˆ’ benchmarksyncmap-bench ^BenchmarkSyncMap demo – v-count =1 -cpuprofile=cpu.profile -memprofile=mem.profile -benchtime=10s

Common parameters are described as follows:

-benchmem: output memory metrics -run: regular, specifying methods that need to be tested -bench: regular, specifying methods that need to be benchmarked -v: output output results and logs even if successful -count: number of times of execution -cpuprofile: -memprofile: output memory profile. -benchtime: execution time. For more parameters, see go help testflagCopy the code
  1. usego toolThe built-in PProf tool analyzes test results. The command is as follows:

go tool pprof -http=:8000 cpu.profile

Common parameters are described as follows:

- HTTP: specify the IP: port, start the web service visual view analysis, the browser will automatically open the page http://localhost:8000/ui/Copy the code

Visual Options menu

Flame figure

Call link diagram

Top function

9.1.2 Web Service Scenario

  1. Using the above global variables, the code example is introducednet/http/pprofPackage, and register each port separately to get pprof data.
package main

import (
	"net/http"
	// Introduce the pprof package
	// _ indicates that only init functions within the package are executed
	_ "net/http/pprof"

	"github.com/gin-gonic/gin"
)

// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0

// Simulate the interface logic
func main(a) {
	r := gin.Default()
	r.GET("/ping".func(c *gin.Context) {
		GlobalVarDemo++
		c.JSON(200, gin.H{
			"message": GlobalVarDemo,
		})
	})
	// Open another port to retrieve pprof data
	go func(a) {
		http.ListenAndServe(": 8888".nil)
	}()
	// Start the Web service
	r.Run()
}
Copy the code
  1. Access the linkhttp://localhost:8888/debug/pprof/, you can see the related profiles.

  1. Using the pprof tool, obtain the remote service profile as follows:

go tool pprof -http=:8000 http://localhost:8888/debug/pprof/profile? seconds=5

Note: the above command, you can use the simulation flow pressure measuring tool, such as command: siege - c 50-100 "http://localhost:8080/ping" tCopy the code

Again, we get this familiar page:

9.2 Using the Trace Tool

Function: Clearly view The execution process of Goroutine in each logical processor, and you can intuitively see The blocking consumption of Goroutine, including network blocking, synchronization blocking (lock), system call blocking, scheduling wait, GC execution time, GC STW(Stop The World) time.

9.2.1 Benchmark Scenario

Use:

To generate a trace.out file:  go test -benchmem -run=^$ -bench ^BenchmarkDemo_NoPool$ demo -v -count=1 -trace=trace.out go test -benchmem -run=^$ -bench ^BenchmarkDemo_Pool$bench -v -count=1 -trace=trace.out Go tool trace -http=127.0.0.1:8000 trace.outCopy the code

Don’t use the sync. The Pool

Using the sync. The Pool

9.2.2 Web Service Scenario

Use:

Also introduce the package NET/HTTP /pprof

package main

import (
	"net/http"
	// Introduce the pprof package
	// _ indicates that only init functions within the package are executed
	_ "net/http/pprof"

	"github.com/gin-gonic/gin"
)

// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0

// Simulate the interface logic
func main(a) {
	r := gin.Default()
	r.GET("/ping".func(c *gin.Context) {
		GlobalVarDemo++
		c.JSON(200, gin.H{
			"message": GlobalVarDemo,
		})
	})
	// Open another port to retrieve pprof data
	go func(a) {
		http.ListenAndServe(": 8888".nil)
	}()
	// Start the Web service
	r.Run()
}

Copy the code

Start the service and run the following command:

1. Generate trace. The out file command: curl http://localhost:8888/debug/pprof/trace? . 20 seconds = > trace out and the above command execution at the same time, the simulation of the request, you can also use ab: siege - 50-100 "http://localhost:8080/ping" t 2 c. To analyze the trace.out file, run the go tool trace -http=127.0.0.1:8000 trace.out shortcut key: w zoom in and e move rightCopy the code

9.3 Using the DLV tool

9.3.1 Benchmark Scenario

Function: breakpoint debugging and so on.

Installation:

go install github.com/go-delve/delve/cmd/dlv@latest
Copy the code

Use:

package main

import(_"net/http/pprof"

	"github.com/gin-gonic/gin"
)

// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0

// Simulate the interface logic
func main(a) {
	r := gin.Default()
	r.GET("/ping".func(c *gin.Context) {
		GlobalVarDemo++
		c.JSON(200, gin.H{
			"message": GlobalVarDemo,
		})
	})
	r.Run()
}

Copy the code

Command line execution command:

dlv debug main.go

Enter debugging. Common debugging commands:

  • (list or L: output code) : list main.go:16
  • (break or B: breakpoint command) : Executebreak main.go:16To the lineGlobalVarDemo++Breaking point
  • (continue or C: to continue) : continue
  • (print or p: print variable) :print GlobalVarDemo
  • (step or s: can enter the function) : step

For more commands, run help.

Simulation request: curl http://localhost:8080/pingCopy the code

9.3.2 Web Service Scenario

Again, this demo

package main

import (
	"github.com/gin-gonic/gin"
)

// Global variables are not destroyed after completing a request, as in PHP
var GlobalVarDemo int32 = 0

// Simulate the interface logic
func main(a) {
	r := gin.Default()
	r.GET("/ping".func(c *gin.Context) {
		GlobalVarDemo++
		c.JSON(200, gin.H{
			"message": GlobalVarDemo,
		})
	})
	// Start the Web service
	r.Run()
}

Copy the code
  • Find the service process IDlsof -i :8080
  • DLV Debugging processdlv attach 36968
  • Enter debug mode and debug the code (same as above)

9.4(extended) Escape analysis

Escape analysis command: go build -gcflags “-m -l” *. Go

package main

type Demo struct{}func main(a) {
	DemoFun()
}

func DemoFun(a) *Demo {
	demo := &Demo{}
	return demo
}

// # command-line-arguments
//./main.go:11:10: &Demo literal escapes to heap <------- Local variable memory is allocated to the heap
Copy the code

9.5(extension) Assembly code

Go run -gcflags -s main.go

# command-line-arguments "".main STEXT nosplit size=1 args=0x0 locals=0x0 0x0000 00000 (... /demo/main.go:6) TEXT "".main(SB), NOSPLIT|ABIInternal, $0-0 0x0000 00000 (... / demo/main. Go: $0, 6) FUNCDATA gclocals ยท 33 cdeccccebe80329f1fdbee7f5874cb (SB) 0 x0000 00000 (... / demo/main. Go: $1, 6) FUNCDATA gclocals ยท 33 cdeccccebe80329f1fdbee7f5874cb (SB) 0 x0000 00000 (... /demo/main.go:6) FUNCDATA $2, Gclocals ยท 33 cdeccccebe80329f1fdbee7f5874cb (SB) 0 x0000 00000 (< unknown line number >) RET 0 x0000 c3 slightly...Copy the code

GOSSAFUNC=main go build main.go

/ssa. HTML <------- This file is generated and opened on the browserCopy the code

conclusion

Finally, let’s summarize the process from PHPer to Gopher, we need to pay attention to the following points:

  • Correspondence between PHP and Go commonly used code blocks
  • Permanent memory
    • Use of global variables
    • resources
      • reuse
      • The release of
      • return
  • Pointer to the
  • concurrent
    • Concurrent security
    • Concurrency control
    • Timeout control
  • Unit testing & benchmarking
  • Performance analysis

This article is long, it is recommended to click the original article at the end of the article, after the collection to view.

Alternatively, connect the collector PC to ๐Ÿ”— tigerb.cn/php2go/