Home
avatar

dxk

Go 入门

学习官网的 Go 教程。

Packages, Variable and Function

Function

Declaration

Use func to declare a function. Note that all semicolons can be leaved out inside Go.

func add(x int, y int) int {
	return x + y
}

Leave Out Parameter Type

We can shorten parameters if they have the same type:

function add(x, y int) int {
	return x + y
}

Multiple Return Values

A function can return any number of results.

function swap(x, y string) (string, string) {
	return y, x
}

Named Return Values

The return statement can be written without arguments. It’s known as a “naked” return.

// there're return variables before return value type 
func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

Naked return can harm readability in long functions.

Variable

The var statement declares variables.

var a = 15
var b, c int
// var i, j int = 1, 2
fmt.Printf("a = %d, b = %d, c = %d", a, b, c) // a = 15, b = 0, c = 0

If an initializer is present, the type can be omitted. The variable will take the type of the initializer.

Short Variable declarations

We can use := in place of a var declaration.

Short assignment statement only can be used inside a function. Outside a function, every statement must begin with a keyword, such as var, func and so on.

func main() {
	x := 3
}

Basic Types

Go’s basic types are

bool

string

int8   // range is [-2**7, 2**7-1]
int16  // range is [-2**15, 2**15-1]
int32  // range is [-2**31, 2**31-1]
int64  // range is [-2**63, 2**63-1]
uint8  // range is [0, 2**8-1]
uint16 // range is [0, 2**16-1]
uint32 // range is [0, 2**32-1]
uint64 // range is [0, 2**64-1]

byte // alias for uint8
rune // alias for int32
int  // int64 on 64-bit systems, or int32 on 32-bit systems
uint // uint64 on 64-bit systems, or uint32 on 32-bit systems
uintptr // the same as uint

float32
float64

complex64
complex128

We can use fmt.Print to print values:

package main

import "fmt"

func main() {
	var a rune = -1 << 31
	fmt.Println("a =", a)                    // a = -2147483648
	fmt.Printf("Type: %T Value: %v\n", a, a) // Type: int32 Value: -2147483648
}

Zero Value

Variable declared without a explicit initial value will be given/assigned a zero value.

Zero values is:

  1. 0 for numeric types
  2. false for the boolean type
  3. "" for strings

Type Conversion

The expression T(v) converts the value v to the type T.

var i int = 42
var f float32 = float32(i)

Type Inference

For variables without specifying an explicit type (untyped variables), their type is inferred from the value on the right hand side.

The default numeric type will be int, float64 or complex128, depending on the precision of the constant.

package main

import "fmt"

func main() {
	g := 0.867 + 0.5i
	fmt.Printf("type of g is %T", g) // complex128
}

Constant

Constants are declared like variables, but with the const keyword.

Constants can be character (byte?), string, boolean, or numeric values.

const PI = 3.14

Flow Control statements: for, if else, switch and defer

For

Go has only one looping construct, the for loop.

Unlike other languages like C, Java or JavaScript, there are no parentheses surrounding the 3 components of the for statement.

package main
import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		fmt.Printf("i = %v\n", i)
	}
}

”for” is Go’s “while”

If you wanna use while, you can just use for.

package main  

import "fmt"

func main() {
	i := 0
	for i < 10 {
		fmt.Printf("i = %d\n", i)
		i++
	}
}

And If you wanna use while (true), just drop/omit the loop condition.

for {}

If

if statements are like for. The parentheses are not needed but the braces are required.

package main

import "fmt"

func main() {
	i := 1
	if i > 0 {
		fmt.Print("i is positive")
	} else {
		fmt.Print("i is negative")
	}
}

If with a short declaration

Like for, the if statement can start with a variable declaration.

And, variables declared inside an if statement, are also available inside else blocks.

package main

import "fmt"

func main() {
	if i := -1; i > 0 {
		fmt.Print("i is positive")
	} else {
		fmt.Print("i is negative")
	}
}

Switch

A switch statement is a shorter way to write if else statements.

Go’s switch is like the one in C, Java or JavaScript, except break, which is redundant in Go. switch will stop immediately when a case is matched.

package main
import "fmt"

func main() {
	switch i := 0; i {
	case 0:
		fmt.Println("i is 0")
		i++
	case 1:
		fmt.Println("i is 1")
		i++
	default:
		fmt.Println("not matched")
	}
}

Switch with No Condition

Yes, switch can run without a condition, just like if. That means switch true, which can be a very clean way to write long if-else chains.

package main

import "fmt"

func main() {
	// or switch i := 0; {}
	i := 0
	switch {
	case i >= 0:
		fmt.Println("i >= 0")
	case i < 0:
		fmt.Println("i < 0")
	}
}

Defer

defer is a very new concept. Note that defer only can be used in front of a function call.

defer is to defer the execution of a function. So you can’t defer a declaration like i := 1, or a calculation statement like i += 1.

A deferred funtion call is not executed until the surrounding function returns.

package main

import "fmt"

func main() {
	defer fmt.Print("world")
	fmt.Print("Hello ")
}

Stacking Defer

Deferred function calls are pushed onto a stack.

package main

import "fmt"

func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Print(i)
	}

	fmt.Println("done")
    // counting
    // done
    // 9876543210
}

More Types: Structs, Slices and Maps

Pointers

A pointer holds the memory address of a value. The pointer’s type is *T for a T value. And it’s zero value is nil.

Use var p *T to declare a pointer.

Once the initializer is present at declaration, the type can be omitted just like basic-type variables.

package main

import "fmt"

func main() {
	var p *int
	fmt.Printf("type is %T, value is %v", p, p) // type is *int, value is <nil>
}

Pointers must be initialized before using. Use & to get the address of its operand as a pointer’s initial value.

package main

import "fmt"

func main() {
	i := 3
	p := &i
	fmt.Printf("type is %T, value is %v", p, p) // type is *int, value is 0xc00000a0e8
}

The * operator denotes the pointer’s underlying value. This is known as dereferencing.

package main

import "fmt"

func main() {
	i := 3
	p := &i
	*p = 5
	fmt.Printf("underlying value is %v", *p) // underlying value is 5
}

Unlike C, Go has no poiner arithmetic.

Structs

You can think of struct as object in JavaScript. Struct is item-ordered, unlike map, which we’ll learn later.

Defining Struct starts with a type.

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{3, 5}
	fmt.Printf("type is %T, value is %v", v, v) // type is main.Vertex, value is {3 5}
}

By convention, struct name and fields name both are in capicals in Go.

Access Fields

Use a dot to access struct fields.

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{3, 5}
	v.Y = 10
	fmt.Print(v) // {3 10}
}

Note that a struct initializer by a struct variable, will be a deep copy (not shallow copy).

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{3, 5}
	v.Y = 10
	v2 := v
	v2.X = 9
	fmt.Print(v, v2) // {3 10} {9 10}
}

Pointers to Structs

If there’s a p pointer which point to a struct, we can use (*p).X to access struct fields.

But, the language permits us instead to write just p.X, without the explicit dereference.

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := &Vertex{3, 4}
	fmt.Print(v.X, (*v).Y) // 3 4
}

Struct Literals

Use struct literals to initial structs.

Fields value can be filled in order, or by the name: value syntax.

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := &Vertex{3, 4}
	v2 := &Vertex{Y: 3}
	fmt.Print(v, v2) // &{3 4} &{0 3}
}

Arrays

Array’s type is [n]T. n is length of the array, which is cannot be omitted.

package main

import "fmt"

func main() {
	var a1 [3]int
	a1[0] = 1
	// array literal
	a2 := [4]int{2, 4, 6}

	fmt.Print(a1, a2) // [1 0 0] [2 4 6 0]
}

An array’s length is part of its type, so arrays can’t be resized.

Slices

In practice, slices are much common than arrays. A slice is a dynamically-sized, flexible view into the elements of an array. The zero value is nil.

A slice is formed by specifying two indices, separated by a colon.

The low bound is included, whereas the high bound is not.

package main

import "fmt"

func main() {
	a := [5]int{1, 3, 5, 7, 9}
	s := a[1:4]
	s[0] = 100
	fmt.Print(s, a) // [100 5 7] [1 100 5 7 9]
}

Slice Literals

Slice literals will create the same array, then build a slice that references it.

// left type declaration can be omitted
var a1 []int = []int{1, 3, 5}

A slice does not store any data, it just describes a section of an underlying array.

Multiple slices that share the same underlying array, will see the same changes.

package main

import "fmt"

func main() {
	var a1 = []int{1, 3, 5}
	a2 := a1
	a2[2] = 7

	fmt.Printf("value is %v, type is %T", a1, a1) // value is [1 3 7], type is []int
}

Slice Defaults

Slice indices can be omitted, and the default values will be used. Zero is for the low bound, and the length of the slice (or array) for the high bound.

package main

import "fmt"

func main() {
	a := [4]int{1, 3, 5, 7}

	s1 := a[:]
	s2 := s1[:3]
	s3 := s2[1:]

	fmt.Printf("value is %v, type is %T", s3, s3) // value is [3 5], type is []int
}

Slice Length and Capacity

A slice has both a length and a capacity.

The length of a slice is the number of elements it contains.

The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.

use len() and cap() functions.

package main

import "fmt"

func main() {
	a := [4]int{1, 3, 5, 7}

	s1 := a[:]
	s2 := s1[:3]
	s3 := s2[1:]
	s4 := s3[0:cap(s3)]

	fmt.Println(len(s1), cap(s1), s1) // 4 4 [1 3 5 7]
	fmt.Println(len(s2), cap(s2), s2) // 3 4 [1 3 5]
	fmt.Println(len(s3), cap(s3), s3) // 2 3 [3 5]
	fmt.Println(len(s4), cap(s4), s4) // 3 3 [3 5 7]
}

Don’t worry about length or capacity these concepts. They matter just a little.

Slices of Slices

Slices can contain any type, including other slices.

package main

import (
	"fmt"
	"strings"
)

func main() {
	board := [][]string{
		{"1", "2", "3"},
		{"4", "5", "6"},
		// note that this statement must end with a comma
		{"7", "8", "9"},
	}

	board[1][1] = "*"

	for i := 0; i < len(board); i++ {
		// func strings.Join(elems []string, sep string) string
		fmt.Printf("line%d: [%s]\n", i+1, strings.Join(board[i], ", "))
		// line1: [1, 2, 3]
		// line1: [4, *, 6]
		// line1: [7, 8, 9]
	}
}

make

The make function allocates a zeroed array and returns a slice.

func make(sliceType, length, capacity) Type

The third argument capacity is optional.

package main

import "fmt"

func main() {
	s := make([]int, 5)
	fmt.Printf("value is %v, type is %T", s, s) // value is [0 0 0 0 0], type is []int
}

append

The append function is used for appending new elements to a slice.

func append(slice []Type, elems ...Type) []Type

append will point to a newly allocated array, if the original underlying array is too small.

package main

import (
	"fmt"
)

func main() {
	s := make([]int, 3)
	fmt.Println(len(s), cap(s)) // 3 3
	s2 := append(s, 4, 5)
	s[0] = 1
	fmt.Print(s, s2) // [1 0 0] [0 0 0 4 5]
}

Otherwise, append won’t allocate memory if the capacity is sufficient.

package main

import (
	"fmt"
)

func main() {
	s := make([]int, 3, 5)
	fmt.Println(len(s), cap(s)) // 3 5
	s2 := append(s, 4, 5)
	s[0] = 1
	fmt.Print(s, s2) // [1 0 0] [1 0 0 4 5]
}

Map

A map maps keys to values.

map has fixed types of keys and values. We can use make to create a map.

Use [key] to get or set an element.

package main

import "fmt"

type Vertex struct {
	X int
	Y string
}

func main() {
	a := make(map[string]Vertex)
	a["k"] = Vertex{0, "3"}
	a["key2"] = Vertex{9, "8"}
	fmt.Print(a) // map[k:{0 3} key2:{9 8}]
}

Map Literals

Map literals just like struct literals.

package main

import "fmt"

type Vertex struct {
	X int
	Y string
}

func main() {
	var a = map[string]Vertex{
		"k": {0, "3"},
	}
	a["key2"] = Vertex{9, "8"}
	fmt.Print(a) // map[k:{0 3} key2:{9 8}]
}

Note that a map’s zero value is nil. And a nil map cannot add any key.

Two-value Assignment

Test if a key is present with a two-value assignment.

ele, ok := m[key]

ok is a boolean value.

Delete one Element

Use delete function to remove one item.

package main

import "fmt"

type Vertex struct {
	X int
	Y string
}

func main() {
	m := make(map[string]Vertex)
	m["a"] = Vertex{2, "3"}
	m["b"] = Vertex{10, "15"}
	delete(m, "b")
	fmt.Print(m)
}

Range

range is used in a for loop.

Two values are returned for each iteration when ranging. The key and the value will be returned over a map, and the index and a copy of the element will over a slice.

package main

import "fmt"

type Vertex struct {
	X int
	Y string
}

func main() {
	s := []string{"hello", "world"}
	for i, v := range s {
		fmt.Println(i, v)
		// 0 hello
		// 1 world
	}

	m := make(map[string]Vertex)
	m["a"] = Vertex{2, "3"}
	m["b"] = Vertex{10, "15"}
	for k, v := range m {
		fmt.Println(k, v)
		// a {2 3}
		// b {10 15}
	}
}

If you wanna skip one item, omit the second variable or use the _ notation.

for i := range s {}
for i, _ := range s {}

_ cannot be used as value or type.

Function as Value

Functions are values too. They can be passed around just like other values.

package main

import "fmt"

func main() {
	myFn := withLogging(add)
	myFn(3, 4)
}

func add(x, y int) int {
	return x + y
}

func withLogging(f func(int, int) int) func(int, int) int {
	return func(i1, i2 int) (r int) {
		fmt.Println("params are", i1, i2) // params are 3 4
		r = f(i1, i2)
		fmt.Println("result is", r) // result is 7
		return
	}
}

Function Closures

A closure is a function references variables from outside its body.

package main

import "fmt"

func main() {
	add := adder()
	add(3)
	add(5)
	fmt.Print(add(2)) // 10
}

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

Methods and Interfaces

Methods

Go does not have classes. But we can still define methods.

A method is a function with a special receiver argument, which is between the func keyword and the function name.

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X float64
	Y float64
}

func (v Vertex) getDistance() float64 {
	return math.Sqrt((v.X*v.X + v.Y*v.Y))
}

func main() {
	d := Vertex{3, 4}.getDistance()
	fmt.Print(d) // 5
}

Receiver Type

The receiver of methods must be a local type. For instance, you can declare a myInt as int.

package main

import (
	"fmt"
)

type myInt int

func (v *myInt) add(a myInt) {
	*v = *v + a
}

func main() {
	a := myInt(5)
	a.add(6)
	fmt.Print(a) // 11
}

Pointer Receivers

Pointer Receivers are more common than value receivers, since methods often need to modify their receiver.

Refer to the example above.

Note that if you use pointer arguments in a function instead of a method, the & is required.

package main

import (
	"fmt"
)

type myInt int

func (v *myInt) add(a myInt) {
	*v = *v + a
}

func add(v *myInt, a myInt) {
	*v = *v + a
}

func main() {
	a := myInt(5)
	// & cannot be omitted
	add(&a, 6)

	b := myInt(6)
	p := &b
	// totally the same as (*p).add
	p.add(7)
	fmt.Print(a, b, *p) // 11 13 13
}

Interfaces

An interface type is defined as a set of method signatures.

Interfaces are implemented implicitly. There is no implements keyword in Go to implement an interface explicitly.

A type implements an interface by implementing its methods.

package main

import "fmt"

type Animal interface{ Speak() }

type Dog struct{ Name string }

type Cat struct{ Name string }

func (d Dog) Speak() { fmt.Println(d.Name, "says: Woof!") }

func (c Cat) Speak() { fmt.Println(c.Name, "says: Meow!") }

func sayHi(a Animal) {
	fmt.Print("Hi. ")
	a.Speak()
}

func main() {
	wangcai := Dog{"Wangcai"}
	lele := Cat{"Lele"}
	var jack Animal
	sayHi(wangcai) // Hi. Wangcai says: Woof!
	sayHi(lele)    // Hi. Lele says: Meow!
	// panic: runtime error
	sayHi(jack)
}

Null Pointer

Note that in the code above, jack is a null pointer which is not a concrete value. The method will be called with a nil receiver.

In this case, we can determine if the value is nil before it’s been used.

func sayHi(a Animal) {
	if a == nil {
		return
	}
}

Empty Interface

One that specifies zero methods is known as the empty interface.

interface{}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

Type Assertions

Use v.(T) to assert a variable’s type. v must be an interface value.

It will return two values, an underlying value and a boolean which denotes whether the assertion succeeded.

If the second return value is false while it’s omitted, the program will be panic. So, do not omit ok when using type assertions.

package main

import "fmt"

type Animal interface{ Speak() }

type Dog struct{ Name string }

type Cat struct{ Name string }

func (d Dog) Speak() { fmt.Println(d.Name, "says: Woof!") }

func (c Cat) Speak() { fmt.Println(c.Name, "says: Meow!") }

func sayHi(a Animal) {
	t, ok := a.(Cat)
	if !ok {
		fmt.Print("oh, not a cat? just shut up")
		return
	}
	fmt.Print("Hi, I'm a cat. ")
	t.Speak()
}

func main() {
	wangcai := Dog{"Wangcai"}
	sayHi(wangcai) // oh, not a cat? just shut up
}

Type Switches

A type switch is like a regular switch statement, but in a type siwtch the cases specify types (not values).

The format is as below.

package main

import "fmt"

type Animal interface{ Speak() }

type Dog struct{ Name string }

type Cat struct{ Name string }

func (d Dog) Speak() { fmt.Println(d.Name, "says: Woof!") }

func (c Cat) Speak() { fmt.Println(c.Name, "says: Meow!") }

func main() {
	var wangcai Animal = Dog{"Wangcai"}
	switch v := wangcai.(type) {
	case Dog:
		fmt.Print("Dog ", v.Name)
	case Cat:
		fmt.Print("Cat ", v.Name)
	}
}

Stringers

One of the most ubiquitous interfaces is the Stringer, defined by the fmt package.

type Stringer interface {
	String() string
}

Once a struct implements the Stringer, the printing message can be customized as you want.

package main

import "fmt"

type People struct {
	Name string
	Age  byte
}

func (p People) String() string {
	return "My name is " + p.Name + ", and I'm " + fmt.Sprint(p.Age) + " years old"
}

func main() {
	fmt.Print(People{"Jack", 18}) // My name is Jack, and I'm 18 years old
}

Errors

The error type is a built-in interface similar to fmt.Stringer.

type error interface {
	Error() string
}

As with fmt.Stringer, the fmt package looks for the error interface when printing values.

We can define a customized error struct by implementing the error interface.

package main

import (
	"fmt"
	"strconv"
)

type TransformError struct {
	original string
}

func (t TransformError) Error() string {
	return fmt.Sprintf("[TransformError] cannot transform `%v` to an integer", t.original)
}

func main() {
	// func strconv.Atoi(s string) (int, error)
	_, err := transform("haha")
	if err != nil {
		fmt.Print(err) // [TransformError] cannot transform `haha` to an integer
	}
}

func transform(s string) (r int, e error) {
	r, err := strconv.Atoi(s)
	if err != nil {
		e = TransformError{s}
	}
	return
}

A nil error denotes success. On the contrary, a non-nil error denotes failure.

Generics

Type parameters of generics appear between brackets.

Generic Functions

Go functions can be written to work on multiple types using *type parameters.

func Index[T comparable](s []T, x T) int

comparable is an interface that is implemented by all comparable types.

Declare a function which returns the index if a match is found.

package main

import (
	"fmt"
)

func Index[T comparable](s []T, x T) int {
	for i, v := range s {
		if v == x {
			return i
		}
	}
	return -1
}

func main() {
	s := []string{"ab", "cd", "ef"}
	// or Index[string](s, "ef")
	fmt.Print(Index(s, "ef")) // 2
}

Generic Types

A type can be parameterized with a type parameter. It can be useful for implementing generic data structures.

package main

import "fmt"

type ListNode[T any] struct {
	val  int
	next *ListNode[T]
}

func main() {
	head := ListNode[int]{5, nil}
	node1 := ListNode[int]{7, nil}
	head.next = &node1
	fmt.Print(head.next.val) // 7
}

Concurrency

Goroutines

A goroutine is a lightweight thread (a coroutine, to be precise) managed by the Go runtime.

Use the keyword go to start a new goroutine.

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("hello")
	go fmt.Println("world")
	time.Sleep(50 * time.Millisecond)
}

Channels

Channels are a typed conduit through which you can send and receive values.

Use make to declare a channel.

ch := make(chan string)

<- is called channel operator. The data flows in the direction of the arrow.

package main

import (
	"fmt"
)

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

	go func() {
		ch <- "Hello"
	}()

	v := <-ch

	fmt.Print(v)
}

Buffered Channels

Channels can be buffered. Just provide the buffer length as the second argument when initializing.

package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	fmt.Println(<-ch) // 1
	ch <- 3
	fmt.Println(<-ch) // 2
	fmt.Println(<-ch) // 3
}

Close

A sender can close a channel to indicate that no more values will be sent. Receivers can assign a second paramater which is a boolean.

Only the sender should close a channel.

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	go func() {
		ch <- 5
		time.Sleep(500 * time.Millisecond)
		close(ch)
	}()
	v, ok := <-ch
	if ok {
		fmt.Println(v) // 5
	}
	v, ok = <-ch
	if ok {
		fmt.Println("if", v)
	} else {
		fmt.Println("else") // else
	}
}

If no close in code, there will be a panic fatal error: all goroutines are asleep - deadlock!.

Range

The loop for v := range ch receives values from the channel repeatedly until it’s closed.

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	go func() {
		ch <- 5
		time.Sleep(500 * time.Millisecond)
		ch <- 6
		time.Sleep(500 * time.Millisecond)
		close(ch)
	}()

	for v := range ch {
		fmt.Println(v) // 5 6
	}
}

Select

The select statement lets a goroutine wait. It blocks untils one of its cases can run.

It chooses one at random if multiple are ready.

package main

import (
	"fmt"
	"time"
)

func main() {
	c1, c2 := make(chan int), make(chan int)
	go func(c chan int) {
		for i := 0; i < 3; i++ {
			c <- i + 1
			time.Sleep(time.Second / 2)
		}
		close(c)
	}(c1)
	go func(c chan int) {
		for i := 10; i < 13; i++ {
			c <- i + 1
			time.Sleep(time.Second / 2)
		}
		close(c)
	}(c2)
	for {
		select {
		case v, ok := <-c1:
			if !ok {
				return
			}
			fmt.Println("c1", v)
		case v, ok := <-c2:
			if !ok {
				return
			}
			fmt.Println("c2", v)
		}
	}
}

The default case will run if no other case is ready.

Mutex

Mutual Exclusion Lock, 互斥锁

Mutex is in sync package, which is used to make sure that only one goroutine can access a variable at a time, to avoid conflicts.

package main

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

var (
	mutex sync.Mutex // 创建一个互斥锁
	count int        // 共享资源
)

func main() {
	for i := 0; i < 1000; i++ {
		go increment()
	}
	time.Sleep(time.Second * 2)

}

func increment() {
	mutex.Lock() // 锁定互斥锁
	count += 1   // 安全地修改共享资源
	fmt.Println("Count:", count) // 如果不加锁,最后结果可能不会到达1000
	mutex.Unlock() // 解锁互斥锁
}

Define a block of code that is surrounded by the Lock and Unlock. This is an atomic operation.

Go