Golang Summary

文章目录

      • Basic
        • Package
        • Functions
          • Function Literal and Closures
        • Variables
        • Basic Types
        • Zero Value
        • Type Conversion
        • Type Inference
        • Constants
      • Control Flow
        • For
        • If
        • Switch
        • Defer
      • More Types
        • Pointers
        • Structs
        • Struct Pointer
        • Arrays
        • Slices
        • Zero Slice
        • Create slice with make
        • Appending a slice
        • Range
        • Maps
        • Mutating Map
        • Function values
        • Function Closure
      • Methods
      • Interface
        • Method Sets
        • Interfaces are implemented implicitly
        • Interface values
        • Empty Interface
        • Type Assertion
        • Type Switch (switch clause for checking type)
        • Stringer
          • Raw String Literals
        • Error
        • Type Embedding
          • inner type promotion
        • Exporting and Unexporting Identifiers
      • Concurrency
        • Using Channel
          • Iterating Over a Channel (for := range)
            • Buffered Channel to Achieve WaitGroup
        • Panic
      • Gotcha's
        • 1. len does not work for slice pointer
        • 2. Use Pointer Receiver for Writing Methods
        • 3. reflect IsNil
        • 4. Named return
      • Packages
      • Testing
      • Builtin
      • Effective Go
      • The Go Memory Model
      • Questions

Basic

Package

Main package
By convention, the package name is the same as the last element of the import path. For instance, the “math/rand” package comprises files that begin with the statement package rand.

A name is exported if it begins with a capital letter, i.e. Pizza

Functions

func add(x int, y int) int {
	return x + y
}
// or
func add(x, y int) int { // input params shortened
}
// multiple results
func swap(x, y string) (string, string) {
	return y,x
}
// naked return, return named return values
func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return 
	// notice, return has empty clause, naked
}
Function Literal and Closures

Function Literal is an anonymous function
Closure is a function enclosing values outside of its scope.

func (input) output {}
// for example
import "http"

/*
This function converts a function fn to http.HandlerFunc.
I have a fn and want to convert to http.HandlerFunc.

*/ 
func makeHandler(fn func(w http.ResponseWriter, r *http.Request, extra string)) http.HandlerFunc {
	return func (w http.ResponseWriter, r *http.Request) { // return is a function literal
		exta := "123" // do some interception / AOP with exta
		// is the construction of extra uses values outside of the Function Literal, then it is also a Closure.
		fn(w, r, extra) 
	}
}

Variables

Variable can be at function package level.

package main
import {
	"fmt"
}
var c, python, java = true, false, "No!"
func main() {
	var i int = 1
	fmt.Println(i, c, python, java)
}

// use := for short assignment declaration in side function inplace of var and no need for TYPE!
func main() {
	var i, j int = 1, 2
	k := 3
	fmt.Println(i, j, k)
}

Basic Types

bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128

Zero Value

0 for numeric values
false for bool and
“” for string type

Type Conversion

Need explicit conversion

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// Or, put more simply:
i := 42
f := float64(i)
u := uint(f) // need to have uint and float64, otherwise runtime error

Type Inference

Either by := or by var with initial value
One can also use new to create an instance of some type T

var someTypeInstance = new(T)
var someSlice []int = new([]int)

Constants

With const
Constants can NOT be initialized with :=
Numeric constants are high precision values

package main
const (
    win = 100
    good = 60
)
func someFunc() {
//xxx
}

Control Flow

For

func main() {
	sum:=0
	for i:=0;i<10;i++ {
		sum += 1
	}
	fmt.Println(sum)
}
// initial value and step value are optional
for ;sum<1000; {
	sum += sum
}
// While style for loop
for sum < 1000 {
	sum += sum
}
// loop forever
for {
}

If

if x < 0 {
}

// If can take a short statement before condition check
if v:= math.Pow(x, n); v<lim {
	return v // v in the statement scope only in the scope
}
return lim // v is no longer available.

Switch

switch os:=runtime.GOOS; os {
	case "darwin": {
	}
	default: {
	}
}
// no break needed, it will stop on a successful match
// switch no condition 
func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

Defer

Executed until surroundings are finished. (like finally)
Deferred function calls are pushed onto a stack.

More Types

Pointers

var i *int 
fmt.Println(*i) // dereferencing
*i = 21 // indirecting

Structs

a collection of fields

type Vertex struct {
	X int
	Y int
}
v:=Vertex{1, 2}
v.X

Struct Pointer

v := Vertex{1,2}
p := &v
(*p).X = 123

var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

Arrays

array is fixed size

[n]T // n values of type T
var a [10]int
primaries := [3]int {2, 3, 5}

Copy array would copy every element of the array.
So, if the element is value type, it copies the value, if the element is pointer, it copies the pointer (memory address)

// !! note the type of the array includes both the length and the data type of the array. 
// So you cannot assign [5]int to [6]int 
valueArray1 := [5]int{}
var valueArray2 [5]int = valueArray1
refArray1 := [...]*string{new(string), ...}
var refArray2 [5]int = refArray1

Pass array with function calls, use array pointer type instead of array type

// array type includes length, so must specify length in the 
// array parameter definition
func arrayFunc(someArray *[1000]string) {
}

Slices

Slices are dynamically sized. Use built-in function make to create a slice

someSlice := make([]string, 5)
// or
someSlice2 := make([]string, 5, 6) // len and cap
// or slice literal, no value for len
someSlice3 := []string{"a", "b"}

arr := []int{1, 2, 3}
slice := arr[:] // create a slice from array

A slice does not store any data, it just describes a section of an underlying array.
Changing the elements of a slice modifies the corresponding elements of its underlying array.
Other slices that share the same underlying array will see those changes.

var a []T
a[low : high]
a[low : high: capacity_index]
// len = high - low
// cap = capacity_index - low
// Note capacity_index must be < capacity of a, otherwise runtime error 
primaries := [6]T{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]

// slice literals
q := []int{2, 3, 5, 7, 11, 13}
s := []struct {
		i int
		b bool
	}{
		{2, true},
		{3, false},
	}

// slice defaults
a[0:10]
a[:10]
a[0:]
a[:]

A slice has both a length and a capacity.

len(s) 
cap(s)
// if len is larger than capacity, it will be runtime error
s1 := []string{}
s2 := []string{}
append(s1, s2...)
// with ... you can append another slice to one slice.

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.

Zero Slice

zero value of slice is nil, which as len and cap of 0 and no underlying array

var nilSlice []int
if nilSlice == nil {}

Create slice with make

Built-in function make to create dynamic size arrays

a := make([]int, 5) // len(a) = 5
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b) = 5
b = b[1:] // len(b) = 4

Slices can contain any type including other slices

Appending a slice

func append([]T, vs... T) []T

// remove last slice is 
slice = slice[:len(slice)-1]

Range

Range in a for loop will give index and copy of the value
:= is like in or enumerated() in python


var pow = [10]int{1, 2, 4, 8, 16}
for idx, val := range pow {
	fmt.Printf("2**%d = %d\n",idx, val)
}
// other variations
for _, val := range pow {}
for idx, _ := range pow {}
for idx := range pow {}

Maps

Zero value of map is nil

var m map[string]Vertex
var m = make(map[string]Vertex) 
// initialize a map of given type ready to use
// map literal
var mapLiteral = map[string]Vertex {
	"bell labs": Vertex {123.5, 234.6},
	"Google": Vertex {11.3, 22.3},
}
// you can omit the type literal if top level type is just a type name
var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

Mutating Map

m[key] = elem
elem = m[key]
delete(m, key)
elem, ok = m[key]
// if key not in m, elem will be nil and ok = false
// if not declared 
elem, ok := m[key]

Function values

Functions are values too. They can be passed around just like other values.
Function values may be used as function arguments and return values.

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}
// input param fn is of type func(float64, float64) float64

Function Closure

Go functions can be closures

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

func fibonacci() func() int {
	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		return b
	}
}

Methods

Go does not have classes. However, you can define methods on types.
A method is a function with a special receiver argument.
The receiver appears in its own argument list between the func keyword and the method name.

import (
	"math"
)
type Vertex struct {
	X, Y float64
}
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
// Abs() is a method on Vertext instance
v:=Vertex{3, 4}
v.Abs()
// Method is just a function with receiver argument

You can declare a method on non-struct types, too.
You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).

type MyFloat float64
func (f MyFloat) Abs() float64 {
	if f < 0 {
		return  float64(-f)
	} 
	return float(f)
}

Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
! Methods with value receiver can NOT modify the value.
So, with value type, the object is copied, but if the method needs to mutate the object, it has to be type pointer

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

Function with pointer only takes pointer parameter (value input will cause error)
Method with pointer can take both pointer and value parameter
However, if a method is defined on value receiver, the receiver is ALWAYS copied
but if the receiver is a pointer, the receiver is shared (no copy).

func (v *Vertex) Scale(factor int) {
// takes both pointer and value
// if called with value, Go interpreter will call it with pointer (&v).Scale(factor)
}

func Scale(v *Vertex, factor int) { // receiver is shared
// only takes 
}

Similarly, a method’s receiver is value, it can be called with either value or pointer; a function called on a value can only be called on value (if called on pointer, it will be runtime error)
Choosing pointer over value is to

  1. be able to mutate pointer value
  2. avoid copying the value upon method call
    In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.

Interface

An interface type is a set of method signatures
A value of interface type can hold any value that implements those methods.

type Abser interface {
	Abs() float64
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}
type Vertex struct {
	X, Y float64
}
func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// Both MyFloat and *Vertex implements Abser interface

Method Sets

Method set is defined as a set of methods associated with type of values or pointers. The type of receiver will determine whether this method is associated with value, pointer or both.

Method Receivers Interface Value or the type value
(t T) T and * T
(t *T) * T

So, if you define interface method on value type, then both value and pointer types have it.
However, if the interface method is on pointer type, only the pointer type has it, you cannot pass the interface value parameter with a value type but only a pointer type

type MyType struct {
}
type MyInterface interface{
	func someMethod() string
}
func (i *MyType) someMethod() string {}
func someCallsite(mi MyInterface) {}
// you cannot call someCallsite with MyType 
// you can only call someCallsite with *MyType 
// i.e. someMethod(&myTypeValue)

Interfaces are implemented implicitly

No “implements” keyword, so no explicit declaration of intent.

Interface values

Under the hood, interface values can be thought of as a tuple of a value and a concrete type:
(value, type)

Nil receiver of interface value will degrade gracefully
However, if an interface of nil (not interface value, aka the concrete type implementing the interface) is called on a method, it trigger runtime error.

type I interface {
	M()
}
type V struct {
	S string
}
type (v *V) M() {
	fmt.Println(v.S)
}
func main() {
	var i I
	describe(i)
	i.M() // runtime error
	i = &V{"some_string"}
	i.M() // valid
	var j *V
	j.M() // ok 
	var k V
	k.M() // error, cause V does not implement I
}

Empty Interface

interface {}

An empty interface may hold values of any type

Type Assertion

Type assertion provide access to interface value’s underlying concrete value.
Note that the variable i must be interface type, not value type; otherwise it will be error.

t := i.(T)
// if i is not of type T, it will trigger a panic 
// to avoid this.
t, ok := i.(T) // ok is assertion success
// if ok == false, t will be zero value of type T

This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.

Type Switch (switch clause for checking type)

func do(i interface{}) {
	switch v := i.(type) { // use type keyword instead of T
	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)
	}
}

Stringer

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

type Stringer interface {
    String() string
}

A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values.

Raw String Literals

Raw string literal is between (back quotes)

var rawString string = `"this is ", 'raw', "string literal"`

Error

built in error type, similar to Stringer

type error interface {
    Error() string
}
i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

Type Embedding

This is to add functions, properties to an existing type to 1. reuse properties and 2. override behaviors. It works like super class with overridden methods

func ()
type user struct { // inner type
	Name string
	Email string
}
type admin struct { // outer type
	user // embeded type
	Level string
}
ad := admin{xxx}
ad.user.notify() 
inner type promotion

Implementation of the inner type is promoted to the outer type if outer type does not implement the interface itself.
However, if outer type implements the interface, if outer type value is passed to a function, the outer type implementation will be used instead of inner type’s. In this case, (similar to subclass overriding), the inner type implementation is not promoted.

Exporting and Unexporting Identifiers

Apply visibility rules

Concurrency

In addition to atomic and sync lib, you can use channel for synchronization
atomic.LoadInt64, atomic.storeInt64, atomic.addInt64 from “sync/atomic”
mutex = sync.Mutex from “sync” with mutex.Lock() and mutex.Unlock()
runtime.Goshed() to schedule back to the queue.

Using Channel

someBufferedChannel := make(chan string, buffersize)
someUnbufferedChannel := make(chan int)

// sending to channel (non-blocking)
someBufferedChannel <- "someStarterString"
// sending to channel (blocking)
someUnbufferedChannel <- 10

// unbuffered channel is waiting(blocked) for the shared variable
someInt := <- someUnbufferedChannel
// buffered channel does not block unless empty
someString := <- someBufferedChannel

close(someBufferedChannel)
close(someUnbufferedChannel)

When close a channel, no more sending but only receiving is allowed.
If a channel is closed and empty, it will immediately return zero-value of the shared variable and state of false

select {
case <- someUnbufferedChannel: {xxx}
case <- someBufferedChannel: {xxx}
default: {xxx}
}

select statement lets a gorouting wait on multiple communication operations and blocks until one of the cases can run. If there are multiple, it will choose one randomly.
If select clause has default, then unbuffered channel is not blocking. If unbuffered channel is empty or not ready, it will immediately use default statement.
Buffered Channel is not blocking usually, so if there is a valid item in the channel or the channel is closed, it will immediately return. However, if channel is not closed and it’s empty, it will be blocking and wait. In this case, with default clause, it will not wait and use default statement immediately.

For both buffered and unbuffered channels, if default clause is there, open empty channels will always go to default immediately.
For both buffered and unbuffered channels, if channel is close, it will not go to default and ok is false.

select {
case _, ok := <- bufferedChan:
	if ok {
		// open, non-empty
	} else {
		// closed
	}
default:
	// open empty
	// if no default clause for open empty, it will be blocking!!
}

select {
case _, ok := <- unbufferedChann:
	if ok {
		// open, non-empty
	} else {
		// closed
	}
}
default:
	// open, empty
Iterating Over a Channel (for := range)

use := range ch to iterate over a channel. If channel is nil, it will block forever. Once channel is closed, it will return; otherwise, the for loop will block indefinitely.

for item := range someChannel {
// will block if channel is empty
}
// will return when channel is closed.
Buffered Channel to Achieve WaitGroup

sync.WaitGroup is to hold the program until all the go routines are finished, by waitGroup.Add() and waitGroup.Done() and waitGroup.Wait()
We can achieve the same behavior with a buffered Channel if we have a known fixed number of goroutines.

func testBufferedChannelWaitGroup(t *testing.T) {
	var maxGoroutineCount = 5
	waitGroupChannel := make(chan int, maxGoroutineCount)
	for i := 0; i < maxGoroutineCount; i++ {
		go func(tag int) {
			fmt.Println("Starting work with tag: ", strconv.Itoa(tag))
			Wait(100, 200)
			waitGroupChannel <- 1
			fmt.Println("Finished work with tag: ", strconv.Itoa(tag))
		}(i)
	}
	for i := 0; i < maxGoroutineCount; i++ {
		<-waitGroupChannel
		fmt.Println("[Channel] did drain one channel item")
	}
	fmt.Println("WaitGroupChannel work all DONE!")
}

Panic

use panic and receover to throw a panic and catch it at runtime without crashing the application.
Note that recover is only allowed in deferred function

func Do() {
	panic("Something went wrong!")
}

func SafelyDo() {
	defer func() {
		if err := receover(); err != nil {
			log("something went wrong: %v", err)
		}
	}()
	Do()
}


Gotcha’s

1. len does not work for slice pointer

len does not work for slice pointer, it only works for array, array pointer and slice but not slice pointer.

arr := [...]int{1, 2, 3} // [...] is only for array literals, not function argument
sli := make([]int, 2, 3)
l = len(arr) // ok
l = len(&arr) // ok 
l = len(sli) // ok
l = len(&sli) // panic!

2. Use Pointer Receiver for Writing Methods

!! Make sure to use pointer receiver for writing methods (changing fields)
Receiver of a method is like a parameter that’s passed by value.
So, if a writing method changes the field/object property, it only changes the copy of the object if passed by value, not the original object!

Only pointer receiver can modify the object it points to. (value receiver cannot)

type Vertex struct {
	X, Y float64
}
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Printf("v is %v \n", v) 
}
  1. When using Type Embedding, cannot use Composite Literals
type A struct {
	a string
}
type B struct {
	A
	b string
}
bb := B{a: "a", b: "b"} // ERROR: a is embedded, so cannot use literal 
// must be
bb := B{b: "b"}
bb.a = "a"

3. reflect IsNil

reflect.IsNil() only works for chan, func, interface, map or slice or pointer. It does NOT work for i.e. string, int or other custom object. (it will cause panic.)

4. Named return

Named return is usually used in defer clause where you want to return something else but you cannot really put “return” because it will just return the goroutine.
Note:

  1. either you do all named return or explicitly return all. Cannot return a subset explicitly and return the other named return.
  2. Must put return in the last even if you use named return
func SomeFunc() (string, error) {
	var someKey string
	var someError error
	defer func() {
		if r:=recover(); r!=nil {
			someKey = ""
			someError = r.(error)
		}
	}()
	return // must have return here even with named return
}

Packages

Testing

  1. Testing file must end with “_test.go”
  2. Testing function must start with “TestXxx”, where Xxx should be capitalized.
  3. Command line
# must be in the package directory
> go test packagename # this is to test all the test files in the package. 
> go test some_test.go # run test again 
> go test # test all the test files in the current dir (??) 
> go test -run ''      # Run all tests.
> go test -run Foo     # Run top-level tests matching "Foo", such as "TestFooBar". 
> # This is to run one specific test function.
> go test -run Foo/A=  # For top-level tests matching "Foo", run subtests matching "A=".
> go test -run /A=1    # For all top-level tests, run subtests matching "A=1".

if Main function is specified, it will be run instead of other tests.

func TestMain(m *testing.M) {
// do set up and tear down
	t.Run("group", func(t *testing.T) {
		t.Run("test1", parallelTest1)
		t.Run("test2", parallelTest1)
		t.Run("test3", parallelTest1)
	})
}

Builtin

  1. new function builds a pointer of the type:
func new(Type) *Type

Effective Go

  1. break multiple layers of loop
Loop:
	for i:=0;i<3;i++ {
		switch {
			case 1: 
				xxx // no need for break
			case 2: 
				xxx
				break Loop // <- break the outer loop
		}
	}
  1. Named return Value
func SomeFunc(r Reader, buf []byte) (n int, err error) {
	for len(buf) > 0 && err==nil {
		var nr int
		nr, err = r.Read(buf)
		n += nr
		buf = buf[nr:]
	}
	return 
	// here, it will return n and err (named)
}
  1. Constructor with new and make
    new is to allocate memory without initialization, it returns a pointer to a newly allocated zero value of type T
p := new(SyncedBuffer) // p type *SyncedBuffer
var v SyncedBuffer // v type SyncedBuffer, not pointer

make is only for map, slice and channels
It returns an initialized instance. i.e. slice, zero value is nil as slices need all 1. type, 2. len and 3. cap to be fully initialized.

s := new([]int) // return nil, zero-valued
ss := make([]int, 10, 10) // returns empty slice 
  1. Switch on type (variable.(type))
switch(a.(type)) {
	default:  // as go needs no break 
	case int: 
	case string:
	case *bool:
}

The Go Memory Model

happens beforeis a partial order of execution of memory operations in a Go program.

  1. in a single goroutine, happens beforeorder is the order expressed by the program. This is not true for multiple go routines.
  2. if a package P imports another package Q, the completion of Q’s init() function happens before the init() function of P.
    Therefore, all init() functions finish before main() of P
  3. Compilers may do optimization to reorder the reads and writes in a single goroutine without affecting the behavior. So,
a = 1
b = 2
// you may expect b = 2 called before a = 1

Goroutine Creation
The go statement that starts a new goroutine happens before the goroutine’s execution begins. (So is the next operation of the goroutine creation)

var a string
func Do() {
    print(a)
}
func main() {
    a = "helloworld!"
    go Do()
}

! The print function will happen sometime in the future, possibly after the program returns

Goroutine Destruction
The exit of a goroutine is not guaranteed to happen before any event in the program

var a string
func hello() {
    go func() { a = "hello" }()
    print(a)
}

The goroutine assignment may not happen before print. Some aggressive compiler may even

Channel Communication

  1. Buffered channel, a send is guaranteed tohappen before a receive
ch := make(chan int, 10)
var a string
func Do() {
    a = "hello world!"
    ch <- 1 
}
func main() {
    go Do()
    <- ch // !! this is blocking
    print(a) // guaranteed to print hello world!
}

  1. Unbuffered channel, a receive is guaranteed to happen beforebefore a send (a send sends first and then acknowledges the receive before return)
ch:=make(chan int)
var a string
func Do() {
    a = "hello world!"
    <- ch 
}
func main() {
    go Do()
    ch <- 1
    print(a) // guaranteed to print hello world!
}

More generally, The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes.

Locks
for both sync.Lock and sync.RWLock, if n < m, then n_th Unlock() happens beforem_th Lock() returns

var l sync.Mutex
var a string
func Do() {
    a = "hello world!"
    l.Unlock()
}
func main() {
    l.Lock()
    go Do()
    l.Lock()
    print(a) // guaranteed to print hello world!
}

More generally, n_th Unlock() happens before (n+1)_th Lock()

Once
A single call from Once happens (returns) before any call of Once returns.

var a string
var once sync.Once
func setup() {
    a = "hello!"
}

func doprint() {
    once.Do(setup)
    print(a)
}
func main() {
    go doprint()
    go doprint()
}

Assignment is in Once and happens before print (in one goroutine), so all Once happens before print, so it will print hello! twice!

General Rule Of Thumb
Without anything from sync or sync/atomic, there is no particular guaranteed ordering between different goroutines!
Use, Mutex, Condition, Once, and preferred Channel to ensure synchronization!

Incorrect Synchronization

  1. read and write
var a, b int

func f() {
    a = 1
    b = 2
}
func g() {
    print(b)
    print(a)
}
func main() {
    go f() // write
    g() // read
}

There is no particular ordering (even in f(), b=2 can happen before a=1), so it’s possible to print 2 then 0 (or two 0’s or 1, 2 or 2, 1, or 1, 0 or 2, 0 or 0, 1 or 0, 2)

  1. Double-checked locking (flag) may be attempted to avoid overhead of synchronization. (Wrong!)
var a string
var done bool

func setup() {
    a = "hello, world"
    done = true // these two can change order
}
func doprint() {
    if !done { // 1
        once.Do(setup) // 2, when one doprint reaches here, the other can be at 1, 2, or 3
    }
    print(a) // 3
}
func twoprint() {
    go doprint()
    go doprint()
}

Since there is no guarantee in the order of the two assingments in setup(), it can print one empty string and one “hello world!”
(one does a setup with done=true first and before a is set, the other goroutine checks done and does not do setup and prints a before the first goroutine finishes setting a)

  1. Use loop to busy waiting for a value
var a string
var done bool

func setup() {
    a = "hello, world"
    done = true
}

func main() {
    go setup()
    for !done {
    }
    print(a)
}

Again, since the ordering in setup() is not guaranteed, so it may print empty string. In some extreme case, compiler may remove done=true in setup because there is no synchronization between the two goroutines, main may never finish.

type T struct {
    msg string
}
var g *T
func setup() {
    t := new(T)
    t.msg = "hello, world"
    g = t
}
func main() {
    go setup()
    for g == nil {
    }
    print(g.msg)
}

More practically

type T struct {
    msg string
}
var g *T
func setup() {
    t := new(T)
    t.msg = "hello, world"
    g = t
}
func main() {
    go setup()
    for g == nil {
    }
    print(g.msg)
}

In setup, g assignment and t.msg assignment are not guaranteed to be in this order. So, it’s possible to print empty string!

Conclusion, when multiple Goroutines communicate with each other (having dependency, sharing objects, passing messages), it’s better to use synchronization from sync package than using if/for with flag checks (which has no guarantee of synchronization).

Questions

  1. Do we need Generics, Reflection in Go? (link and here)

Reference

Specs
https://golang.org/ref/spec#Introduction

你可能感兴趣的:(Golang Summary)