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