Go Basics

Go’s characteristics: simple, efficient, concurrency-friendly.

Installation

Go official site: https://golang.google.cn/

Installation guide: Download and install

# Download Go from the official site first
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.25.5.linux-amd64.tar.gz

# Set environment variable
export PATH=$PATH:/usr/local/go/bin

# Check version
go version

Learning Resources

Golang Chinese learning docs: https://golang.halfiisland.com/

Creating a Project

# Create project directory
mkdir mydata

cd mydata

# Initialize the module
go mod init mydata

# Create main.go
touch main.go

# Write code
vim main.go

Example:

package main   // package name
import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Clean up unused dependencies in go.mod:

go mod tidy

Build:

go build main.go

Run:

go run main.go

Go Fundamentals

Packages

In Go, programs are built by linking packages together. The most fundamental unit of importing in Go is a package, not a .go file. A package is essentially a directory (folder). All variables, constants, and defined types are shared within a package. Package names should be lowercase and as short as possible. The package keyword declares which package the current .go file belongs to.

cmd    # cmd package — the directory name is the package name
--> a.go   # package cmd
--> b.go   # package cmd

Everything is shared within a package, but not necessarily visible from outside. Sometimes you want to hide a type from external access, so visibility control is needed. Package visibility rules:

  • Names starting with an uppercase letter are public (exported) types/variables/constants.
  • Names starting with a lowercase letter or underscore are private (unexported).

Importing a package to use its types, methods, functions, or variables uses the import keyword followed by the package name:

import "fmt"

By convention, a package named internal inside any package is an internal package — external packages cannot access anything within it, or the code will not compile.

Functions

Function declaration:

func functionName([parameter list]) [return values] {
  function body
}

Variable declaration, var variableName typeName:

var intNum int

Pointers: Go retains pointers, which preserves performance to some extent, while also restricting pointer usage for better GC and safety. The two common pointer operators are the address-of operator & and the dereference operator *.

Deferred calls: The defer keyword schedules a function call to execute just before the enclosing function returns. Deferred functions are executed in LIFO order.

func main() {
  Do()
}

func Do() {
  defer func() {
    fmt.Println("1")   // executed last
  }()
  fmt.Println("2")   // executed first
}

The init function is a special initialization function used to execute package-level initialization logic at program startup. It does not need to be called manually — the Go runtime invokes it automatically.

Concurrency

Go has first-class support for concurrency — it’s at the core of the language. The learning curve is relatively gentle; developers can build decent concurrent applications without worrying too much about low-level details, which raises the floor for developers.

Goroutines: A goroutine (coroutine) is a lightweight thread — a user-space thread not directly scheduled by the OS, but by Go’s own runtime scheduler. This makes context-switching overhead very small, which is one reason Go’s concurrency performance is so good.

In Go, creating a goroutine is extremely simple — just use the go keyword followed by a function call:

func main() {
  go fmt.Println("hello world!")
  go hello()
  go func() {
    fmt.Println("hello world!")
  }()
}

func hello() {
  fmt.Println("hello world!")
}

Go provides many concurrency control mechanisms. The three most common are:

  • channel: typed conduit for communication
  • WaitGroup: semaphore-like counter for waiting on a group of goroutines
  • Context: for propagating cancellation and deadlines across goroutine hierarchies Each has different use cases: WaitGroup works well for dynamically controlling a fixed set of goroutines; Context is better for deeply nested goroutine trees; channels are ideal for goroutine-to-goroutine communication. Go also supports traditional lock-based synchronization:
  • Mutex: mutual exclusion lock
  • RWMutex: reader/writer mutual exclusion lock

Channels: Channels enable communication by sharing memory through messages — they are the idiomatic way for goroutines to communicate.

Channels are created exclusively with the built-in make function, which takes the channel type as its first argument and an optional buffer size as the second:

intCh := make(chan int)
// buffered channel with capacity 1
strCh := make(chan string, 1)

Always close a channel when you are done with it, using the built-in close function:

func close(c chan<- Type)

func main() {
  intCh := make(chan int)    // create channel
  // do something
  close(intCh)  // close channel
}

Go uses two intuitive operators for channel read/write:

ch <- data    // send data to a channel
variable := <-ch  // receive data from a channel

Example:

func main() {
  // create an unbuffered channel
  ch := make(chan int)
  defer close(ch)
  go func() {
    // send data
    ch <- 123
  }()
  // receive data
  n := <-ch
  fmt.Println(n)
}

Data flows through a channel in FIFO order (like a queue). Operations on a channel are synchronous — at any given moment, only one goroutine can send data to a channel, and only one goroutine can receive data from it.


References: Go Language Design and Implementation Go Analysis