作为一名长期深耕Java生态的开发者,你或许早已习惯了JVM的繁琐配置、GC的调优难题、高并发场景下线程池的复杂管控。而Go语言(Golang)自2009年由Google推出以来,凭借"简单、高效、天生支持并发"的特性,迅速成为云原生、微服务、高并发后端领域的"新宠"。相比于Java,Go无需厚重的运行时,编译后直接生成可执行文件,部署仅需一个二进制包;并发模型基于goroutine(轻量级线程),数万级并发轻松应对,资源消耗远低于Java线程。
一、Go语言入门前的准备:环境搭建
1.1 为什么选择Go?
先明确Go的核心优势(权威依据:Go官方文档https://go.dev/doc/):
-
高性能:编译型语言,接近C/C++的执行效率,远高于Java的解释执行(JVM);
-
极简语法:比Java少80%的冗余语法,学习成本低,上手快;
-
原生并发:goroutine+channel的CSP并发模型,无需手动管理线程池;
-
跨平台编译:一行命令编译出任意平台的可执行文件(Windows/Linux/Mac);
-
零依赖部署:编译后生成单一二进制文件,无需安装运行时,部署像复制文件一样简单;
-
丰富的标准库:内置net/http、encoding/json、database/sql等核心库,无需依赖第三方包即可完成大部分开发。
1.2 环境搭建(以Linux/Mac为例,Windows同理)
Go的最新稳定版本为1.22.0(权威来源:Go官方下载页https://go.dev/dl/),安装步骤如下:
# 1. 下载安装包(Linux 64位)
wget https://dl.google.com/go/go1.22.0.linux-amd64.tar.gz
# 2. 解压到/usr/local目录
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz
# 3. 配置环境变量(编辑~/.bashrc或~/.zshrc)
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
echo 'export GOPATH=$HOME/go' >> ~/.bashrc
echo 'export GO111MODULE=on' >> ~/.bashrc
echo 'export GOPROXY=https://goproxy.cn,direct' >> ~/.bashrc
# 4. 生效环境变量
source ~/.bashrc
# 5. 验证安装
go version
输出go version go1.22.0 linux/amd64即为成功。
补充:Windows用户直接下载msi安装包,双击安装即可,安装程序会自动配置环境变量。
1.3 Go的开发工具
推荐使用VS Code(安装Go插件)或Goland,VS Code的Go插件提供语法高亮、自动补全、调试等功能,完全满足入门需求。
二、Go语言核心语法:从Hello World到基础语法
2.1 第一个Go程序:Hello World
创建文件hello.go,内容如下:
package main // 主包,可执行程序必须以main为包名
import "fmt" // 导入格式化输入输出包
// 主函数,程序入口
func main() {
fmt.Println("Hello, Go!") // 打印输出
}
运行方式:
# 直接运行
go run hello.go
# 编译为二进制文件
go build hello.go
# 运行编译后的文件(Linux/Mac)
./hello
# Windows
hello.exe
输出:Hello, Go!
核心解释:
-
package:Go的包管理机制,每个Go文件都属于一个包,main包是唯一可执行的包; -
import:导入依赖的包,fmt是Go标准库中用于格式化I/O的包; -
func main():程序的入口函数,无参数、无返回值,必须在main包中; -
fmt.Println:打印字符串并换行,区别于fmt.Print(不换行)。
2.2 变量与常量
2.2.1 变量声明
Go是静态类型语言,变量声明后类型不可变,有三种声明方式:
// 方式1:指定类型,显式声明
var name string = "ken"
var age int = 30
// 方式2:类型推导,由值自动推断类型
var height = 1.80
// 方式3:短变量声明(仅在函数内可用)
score := 95.5
示例代码(var_demo.go):
package main
import "fmt"
func main() {
// 单个变量声明
var username string = "jam"
fmt.Println("用户名:", username)
// 多个变量同时声明
var a, b int = 10, 20
fmt.Println("a =", a, ", b =", b)
// 类型推导
var c = 3.14
fmt.Printf("c的类型:%T,值:%v\n", c, c) // %T打印类型,%v打印值
// 短变量声明
d := "Go语言"
fmt.Println("d =", d)
// 变量零值:声明未赋值的变量会有默认零值
var e int
var f string
var g bool
fmt.Printf("e的零值:%d,f的零值:%q,g的零值:%t\n", e, f, g)
}
运行结果:
用户名: jam
a = 10 , b = 20
c的类型:float64,值:3.14
d = Go语言
e的零值:0,f的零值:"",g的零值:false
2.2.2 常量声明
常量使用const关键字,值不可修改,支持字符、字符串、布尔、数值类型:
示例代码(const_demo.go):
package main
import "fmt"
// 全局常量
const PI = 3.1415926
const (
STATUS_SUCCESS = 0
STATUS_ERROR = 1
)
func main() {
// 局部常量
const MAX_SIZE = 100
fmt.Printf("PI = %v\n", PI)
fmt.Printf("成功状态码:%d,错误状态码:%d\n", STATUS_SUCCESS, STATUS_ERROR)
fmt.Printf("最大长度:%d\n", MAX_SIZE)
// iota常量生成器:自增常量,每行+1
const (
A = iota // 0
B // 1
C // 2
)
fmt.Printf("A = %d, B = %d, C = %d\n", A, B, C)
}
运行结果:
PI = 3.1415926
成功状态码:0,错误状态码:1
最大长度:100
A = 0, B = 1, C = 2
2.3 数据类型
Go的基础数据类型分为四大类(权威依据:Go官方文档https://go.dev/ref/spec#Types):
-
数值类型:
-
整数:int(随系统位数,32/64位)、int8/16/32/64、uint(无符号)、uint8(byte)、uint16/32/64、uintptr;
-
浮点数:float32、float64(默认);
-
复数:complex64、complex128。
-
-
布尔类型:bool(true/false,不可用0/1替代)。
-
字符串类型:string(UTF-8编码,不可变)。
-
派生类型:指针、数组、切片、映射、通道、结构体、接口、函数。
示例代码(type_demo.go):
package main
import "fmt"
func main() {
// 整数
var num1 int8 = 127 // int8范围:-128~127
var num2 uint8 = 255 // uint8范围:0~255
fmt.Printf("num1 = %d, num2 = %d\n", num1, num2)
// 浮点数
var f1 float32 = 3.14
var f2 float64 = 2.71828
fmt.Printf("f1 = %f, f2 = %.5f\n", f1, f2)
// 字符串
var str1 string = "Go语言入门"
var str2 = `多行字符串
使用反引号
无需转义`
fmt.Println("str1 =", str1)
fmt.Println("str2 =", str2)
// 字符串操作
fmt.Printf("字符串长度:%d\n", len(str1)) // len返回字节数,UTF-8中一个中文字符占3字节
// 遍历字符串(按字符)
for i, c := range str1 {
fmt.Printf("索引%d:%c(Unicode码点:%d)\n", i, c, c)
}
}
运行结果:
num1 = 127, num2 = 255
f1 = 3.140000, f2 = 2.71828
str1 = Go语言入门
str2 = 多行字符串
使用反引号
无需转义
字符串长度:12
索引0:G(Unicode码点:71)
索引1:o(Unicode码点:111)
索引2:语(Unicode码点:35821)
索引5:言(Unicode码点:35328)
索引8:入(Unicode码点:20843)
索引11:门(Unicode码点:38376)
2.4 流程控制
Go的流程控制语法简洁,去除了Java中的do-while、switch的break陷阱,新增了for-range遍历。
2.4.1 条件语句(if-else)
Go的if语句无需括号,条件后必须紧跟大括号(即使只有一行):
示例代码(if_demo.go):
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
// if初始化语句:在条件前声明变量,作用域仅在if块内
if age := 18; age >= 18 {
fmt.Println("成年")
} else {
fmt.Println("未成年")
}
}
运行结果:
良好
成年
2.4.2 循环语句(for)
Go只有for循环,替代了Java的for、while、do-while:
示例代码(for_demo.go):
package main
import "fmt"
func main() {
// 标准for循环
for i := 0; i < 5; i++ {
fmt.Print(i, " ")
}
fmt.Println()
// while风格:省略初始化和后置语句
j := 0
for j < 5 {
fmt.Print(j, " ")
j++
}
fmt.Println()
// 无限循环:省略条件
k := 0
for {
if k >= 5 {
break
}
fmt.Print(k, " ")
k++
}
fmt.Println()
// for-range遍历:字符串、数组、切片、映射等
str := "Go语言"
for index, char := range str {
fmt.Printf("索引%d:%c\n", index, char)
}
// 跳过元素(continue)
for i := 0; i < 5; i++ {
if i == 2 {
continue
}
fmt.Print(i, " ")
}
fmt.Println()
}
运行结果:
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
索引0:G
索引1:o
索引2:语
索引5:言
0 1 3 4
2.4.3 选择语句(switch)
Go的switch更灵活,无需break,默认case结束后自动退出,支持任意类型、表达式:
示例代码(switch_demo.go):
package main
import "fmt"
func main() {
// 基础switch
day := 3
switch day {
case 1:
fmt.Println("周一")
case 2:
fmt.Println("周二")
case 3:
fmt.Println("周三")
case 4, 5: // 多个case合并
fmt.Println("周四/周五")
default:
fmt.Println("周末")
}
// 无表达式switch(替代if-else)
score := 75
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
// fallthrough:强制执行下一个case
num := 1
switch num {
case 1:
fmt.Println("1")
fallthrough
case 2:
fmt.Println("2")
case 3:
fmt.Println("3")
}
}
运行结果:
周三
及格
1
2
三、Go的核心特性:函数、结构体与接口
3.1 函数
Go的函数是一等公民,支持多返回值、可变参数、匿名函数、闭包,语法比Java简洁。
3.1.1 函数声明
语法:func 函数名(参数列表) (返回值列表) { 函数体 }
示例代码(function_demo1.go):
package main
import "fmt"
// 无参数无返回值
func sayHello() {
fmt.Println("Hello, Go Function!")
}
// 单参数单返回值
func add(a int, b int) int {
return a + b
}
// 简写参数类型(同类型参数)
func subtract(a, b int) int {
return a - b
}
// 多返回值(常用:返回结果+错误)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为0") // 返回错误
}
return a / b, nil // nil表示无错误
}
// 命名返回值(提前声明返回值变量)
func multiply(a, b int) (result int) {
result = a * b // 直接赋值给返回值变量
return // 无需指定返回值
}
func main() {
sayHello()
sum := add(10, 20)
fmt.Println("10+20 =", sum)
diff := subtract(20, 10)
fmt.Println("20-10 =", diff)
// 接收多返回值
res, err := divide(10, 2)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("10/2 =", res)
}
// 除数为0的情况
res2, err2 := divide(10, 0)
if err2 != nil {
fmt.Println("错误:", err2)
} else {
fmt.Println("10/0 =", res2)
}
prod := multiply(10, 20)
fmt.Println("10*20 =", prod)
}
运行结果:
Hello, Go Function!
10+20 = 30
20-10 = 10
10/2 = 5
错误: 除数不能为0
10*20 = 200
3.1.2 可变参数与匿名函数
示例代码(function_demo2.go):
package main
import "fmt"
// 可变参数(必须是最后一个参数)
func sum(nums ...int) int {
total := 0
for _, num := range nums { // _忽略索引
total += num
}
return total
}
func main() {
// 可变参数调用
fmt.Println("sum(1,2) =", sum(1, 2))
fmt.Println("sum(1,2,3,4) =", sum(1, 2, 3, 4))
// 切片传参到可变参数
nums := []int{1,2,3,4,5}
fmt.Println("sum(nums...) =", sum(nums...))
// 匿名函数(无函数名,直接调用)
func(msg string) {
fmt.Println("匿名函数:", msg)
}("Hello, Anonymous Function!")
// 闭包:匿名函数引用外部变量
counter := func() func() int {
count := 0
return func() int {
count++
return count
}
}()
fmt.Println("闭包计数1:", counter())
fmt.Println("闭包计数2:", counter())
fmt.Println("闭包计数3:", counter())
}
运行结果:
sum(1,2) = 3
sum(1,2,3,4) = 10
sum(nums...) = 15
匿名函数: Hello, Anonymous Function!
闭包计数1: 1
闭包计数2: 2
闭包计数3: 3
3.2 结构体与方法
Go没有类(class),但通过结构体(struct)+方法(method)实现面向对象的核心特性。
3.2.1 结构体声明与初始化
示例代码(struct_demo1.go):
package main
import "fmt"
// 声明结构体(类似Java的POJO)
type User struct {
ID int
Username string
Age int
Email string
}
func main() {
// 方式1:按字段顺序初始化
u1 := User{1, "jam", 30, "jam@example.com"}
fmt.Println("u1 =", u1)
// 方式2:指定字段名初始化(推荐,顺序无关)
u2 := User{
ID: 2,
Username: "ken",
Age: 28,
Email: "ken@example.com",
}
fmt.Println("u2 =", u2)
// 方式3:零值初始化,后续赋值
var u3 User
u3.ID = 3
u3.Username = "go_dev"
fmt.Println("u3 =", u3)
// 结构体指针
u4 := &User{4, "pointer", 25, "pointer@example.com"}
fmt.Println("u4 =", *u4)
// 指针访问字段,Go自动解引用(无需->)
fmt.Println("u4.Username =", u4.Username)
}
运行结果:
u1 = {1 jam 30 jam@example.com}
u2 = {2 ken 28 ken@example.com}
u3 = {3 go_dev 0 }
u4 = {4 pointer 25 pointer@example.com}
u4.Username = pointer
3.2.2 结构体方法
方法是绑定到结构体的函数,语法:func (接收者) 方法名(参数) 返回值 { 方法体 }
示例代码(struct_demo2.go):
package main
import "fmt"
type User struct {
ID int
Username string
Age int
}
// 值接收者:方法操作的是结构体副本
func (u User) GetUsername() string {
return u.Username
}
// 指针接收者:方法操作的是结构体本身(推荐,避免拷贝)
func (u *User) SetAge(newAge int) {
u.Age = newAge
}
// 结构体方法:判断是否成年
func (u *User) IsAdult() bool {
return u.Age >= 18
}
func main() {
u := User{1, "jam", 17}
fmt.Println("用户名:", u.GetUsername())
fmt.Println("是否成年:", u.IsAdult())
// 调用指针接收者方法,Go自动转换为指针
u.SetAge(18)
fmt.Println("修改后的年龄:", u.Age)
fmt.Println("是否成年:", u.IsAdult())
}
运行结果:
用户名: jam
是否成年: false
修改后的年龄: 18
是否成年: true
3.3 接口
Go的接口是"鸭子类型"(只要实现了接口的所有方法,就隐式实现了该接口),无需显式声明implements,比Java的接口更灵活。
3.3.1 接口声明与实现
示例代码(interface_demo.go):
package main
import "fmt"
// 声明接口:定义方法签名
type Animal interface {
Speak() string // 无参数,返回string
}
// 定义结构体
type Dog struct {
Name string
}
type Cat struct {
Name string
}
// Dog实现Animal接口(隐式)
func (d Dog) Speak() string {
return d.Name + ":汪汪汪"
}
// Cat实现Animal接口(隐式)
func (c Cat) Speak() string {
return c.Name + ":喵喵喵"
}
// 接收Animal接口的函数
func MakeSound(a Animal) {
fmt.Println(a.Speak())
}
func main() {
dog := Dog{Name: "旺财"}
cat := Cat{Name: "咪咪"}
MakeSound(dog)
MakeSound(cat)
// 接口类型变量
var animal Animal
animal = dog
fmt.Println(animal.Speak())
animal = cat
fmt.Println(animal.Speak())
}
运行结果:
旺财:汪汪汪
咪咪:喵喵喵
旺财:汪汪汪
咪咪:喵喵喵
四、Go的并发编程:goroutine与channel
并发是Go的核心优势,也是区别于Java的关键特性。Java的并发基于线程(重量级,每个线程占1-2MB栈空间),而Go的goroutine是轻量级线程(初始栈仅2KB,可动态扩缩容),单机可轻松创建数万个goroutine。
4.1 并发模型:CSP
Go的并发模型基于CSP(通信顺序进程),核心思想:"不要通过共享内存来通信,而要通过通信来共享内存"。
-
goroutine:轻量级执行体,由Go运行时管理,而非操作系统内核;
-
channel:goroutine间的通信管道,用于安全传递数据,替代共享内存。
flowchart TD
A[主goroutine]-->B[创建子goroutine1]
A-->C[创建子goroutine2]
B-->D[channel]
C-->D
D-->A[数据汇总]
4.2 goroutine 的使用
启动goroutine只需在函数调用前加go关键字:
示例代码(goroutine_demo1.go):
package main
import (
"fmt"
"time"
)
// 耗时任务
func task(name string) {
for i := 0; i < 5; i++ {
fmt.Printf("任务%s:执行%d次\n", name, i+1)
time.Sleep(100 * time.Millisecond) // 模拟耗时
}
}
func main() {
// 启动goroutine
go task("A")
go task("B")
// 主goroutine等待子goroutine执行完成,否则主goroutine退出后子goroutine也会终止
time.Sleep(1 * time.Second)
fmt.Println("所有任务执行完成")
}
运行结果:
任务A:执行1次
任务B:执行1次
任务A:执行2次
任务B:执行2次
任务A:执行3次
任务B:执行3次
任务A:执行4次
任务B:执行4次
任务A:执行5次
任务B:执行5次
所有任务执行完成
核心注意点(权威依据:Go官方并发文档https://go.dev/doc/effective_go#concurrency):
-
主goroutine的生命周期 :主goroutine退出后,无论子goroutine是否执行完成,都会被强制终止。上面的示例中
time.Sleep(1 * time.Second)是简单的等待方式,但实际开发中不推荐(无法精准控制等待时间)。 -
goroutine的调度:goroutine由Go运行时(runtime)的M:N调度器管理,将M个goroutine映射到N个操作系统线程,调度开销远低于操作系统线程。
-
goroutine的栈:初始栈大小为2KB,可动态扩展(最大可达1GB),而Java线程栈默认1MB且固定,这也是goroutine更轻量的核心原因。
优雅等待goroutine:sync.WaitGroup
实际开发中,使用sync.WaitGroup来等待多个goroutine完成,替代硬编码的time.Sleep:
示例代码(goroutine_demo2.go):
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// 耗时任务
func task(name string) {
defer wg.Done() // 任务完成后调用,计数器-1
for i := 0; i < 5; i++ {
fmt.Printf("任务%s:执行%d次\n", name, i+1)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
// 设置等待组计数器,启动N个goroutine就设为N
wg.Add(2)
go task("A")
go task("B")
// 阻塞主goroutine,直到计数器归0
wg.Wait()
fmt.Println("所有任务执行完成")
}
运行结果与上例一致,但无需依赖time.Sleep,更可靠。
4.3 channel:goroutine间的通信管道
channel是Go实现CSP并发模型的核心,用于在goroutine间安全传递数据,避免共享内存带来的竞态条件。
4.3.1 channel的声明与初始化
语法:var 变量名 chan 数据类型 或 make(chan 数据类型, 缓冲大小)
-
无缓冲channel:
make(chan int),发送和接收会阻塞,直到对方准备好; -
有缓冲channel:
make(chan int, 10),缓冲区未满时发送不阻塞,缓冲区未空时接收不阻塞。
示例代码(channel_demo1.go):
package main
import "fmt"
func main() {
// 声明并初始化无缓冲channel
ch := make(chan string)
// 启动goroutine发送数据
go func() {
ch <- "Hello, Channel!" // 发送数据到channel,阻塞直到有接收者
}()
// 接收channel数据,阻塞直到有发送者
msg := <-ch
fmt.Println("接收到的数据:", msg)
// 关闭channel(关闭后仍可接收数据,不可发送)
close(ch)
}
运行结果:
接收到的数据: Hello, Channel!
4.3.2 有缓冲channel示例
示例代码(channel_demo2.go):
package main
import "fmt"
func main() {
// 初始化有缓冲channel,缓冲区大小为3
ch := make(chan int, 3)
// 发送数据到缓冲区,未填满时不阻塞
ch <- 1
ch <- 2
ch <- 3
fmt.Println("缓冲区已填满,长度:", len(ch), "容量:", cap(ch))
// 接收数据,缓冲区未空时不阻塞
fmt.Println("接收:", <-ch)
fmt.Println("缓冲区剩余长度:", len(ch))
// 继续发送(缓冲区有空位)
ch <- 4
fmt.Println("缓冲区长度:", len(ch))
// 关闭channel
close(ch)
// 遍历channel(关闭后可遍历剩余数据)
for num := range ch {
fmt.Println("遍历接收:", num)
}
}
运行结果:
缓冲区已填满,长度: 3 容量: 3
接收: 1
缓冲区剩余长度: 2
缓冲区长度: 3
遍历接收: 2
遍历接收: 3
遍历接收: 4
4.3.3 单向channel与channel的关闭检测
单向channel用于限制channel的使用场景(仅发送或仅接收),提升代码安全性:
示例代码(channel_demo3.go):
package main
import "fmt"
// 仅发送数据的函数(参数为单向发送channel)
func sendData(ch chan<- int) {
for i := 0; i < 3; i++ {
ch <- i
fmt.Println("发送:", i)
}
close(ch) // 发送完成后关闭channel
}
// 仅接收数据的函数(参数为单向接收channel)
func recvData(ch <-chan int) {
// 循环接收,直到channel关闭
for {
num, ok := <-ch // ok为false表示channel已关闭且无数据
if !ok {
fmt.Println("channel已关闭,接收完成")
break
}
fmt.Println("接收:", num)
}
}
func main() {
ch := make(chan int)
go sendData(ch)
recvData(ch)
}
运行结果:
发送: 0
接收: 0
发送: 1
接收: 1
发送: 2
接收: 2
channel已关闭,接收完成
4.3.4 channel的经典应用:goroutine池
goroutine池用于限制并发数,避免创建过多goroutine导致资源耗尽,是实际开发中的高频场景:
示例代码(channel_pool_demo.go):
package main
import (
"fmt"
"sync"
"time"
)
// 任务结构体
type Task struct {
ID int
Msg string
}
// 执行任务
func executeTask(task Task, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("执行任务%d:%s\n", task.ID, task.Msg)
time.Sleep(500 * time.Millisecond) // 模拟任务耗时
}
func main() {
const poolSize = 3 // goroutine池大小
const taskCount = 10 // 总任务数
var wg sync.WaitGroup
// 创建任务通道
taskChan := make(chan Task, taskCount)
// 启动goroutine池
for i := 0; i < poolSize; i++ {
go func(workerID int) {
for task := range taskChan {
fmt.Printf("工作goroutine%d开始处理任务%d\n", workerID, task.ID)
executeTask(task, &wg)
fmt.Printf("工作goroutine%d完成任务%d\n", workerID, task.ID)
}
}(i)
}
// 提交任务
wg.Add(taskCount)
for i := 0; i < taskCount; i++ {
taskChan <- Task{
ID: i + 1,
Msg: fmt.Sprintf("任务内容%d", i+1),
}
}
close(taskChan) // 关闭任务通道,避免goroutine阻塞
// 等待所有任务完成
wg.Wait()
fmt.Println("所有任务执行完毕")
}
运行结果(部分):
工作goroutine0开始处理任务1
执行任务1:任务内容1
工作goroutine1开始处理任务2
执行任务2:任务内容2
工作goroutine2开始处理任务3
执行任务3:任务内容3
工作goroutine0完成任务1
工作goroutine0开始处理任务4
执行任务4:任务内容4
工作goroutine1完成任务2
工作goroutine1开始处理任务5
执行任务5:任务内容5
工作goroutine2完成任务3
工作goroutine2开始处理任务6
执行任务6:任务内容6
...
所有任务执行完毕
4.4 同步原语:sync.Mutex与sync.RWMutex
虽然Go推荐用channel实现通信,但某些场景下仍需共享内存(如计数器),此时需用互斥锁避免竞态条件:
4.4.1 sync.Mutex(互斥锁)
示例代码(mutex_demo.go):
package main
import (
"fmt"
"sync"
)
var (
counter int
mutex sync.Mutex
wg sync.WaitGroup
)
// 累加计数器
func increment() {
defer wg.Done()
for i := 0; i < 1000; i++ {
mutex.Lock() // 加锁,独占资源
counter++ // 临界区:共享资源操作
mutex.Unlock() // 解锁
}
}
func main() {
wg.Add(2)
go increment()
go increment()
wg.Wait()
fmt.Println("最终计数器值:", counter) // 正确结果应为2000,不加锁会小于2000
}
运行结果:
最终计数器值: 2000
4.4.2 sync.RWMutex(读写锁)
读写锁适用于"读多写少"场景,允许多个读操作并发,写操作独占:
示例代码(rwmutex_demo.go):
package main
import (
"fmt"
"sync"
"time"
)
var (
data = "初始数据"
rwMutex sync.RWMutex
wg sync.WaitGroup
)
// 读操作(并发安全)
func readData(id int) {
defer wg.Done()
rwMutex.RLock() // 加读锁
defer rwMutex.RUnlock()
fmt.Printf("读goroutine%d:读取到数据:%s\n", id, data)
time.Sleep(100 * time.Millisecond) // 模拟读耗时
}
// 写操作(独占)
func writeData(newData string) {
defer wg.Done()
rwMutex.Lock() // 加写锁
defer rwMutex.Unlock()
fmt.Println("写goroutine:开始修改数据")
data = newData
time.Sleep(500 * time.Millisecond) // 模拟写耗时
fmt.Println("写goroutine:数据修改完成,新数据:", data)
}
func main() {
// 启动5个读goroutine
for i := 0; i < 5; i++ {
wg.Add(1)
go readData(i)
}
// 启动1个写goroutine
wg.Add(1)
go writeData("修改后的数据")
// 再启动5个读goroutine
for i := 5; i < 10; i++ {
wg.Add(1)
go readData(i)
}
wg.Wait()
fmt.Println("所有操作完成")
}
运行结果(核心特征:写操作期间读操作阻塞,写完成后读操作并发执行):
读goroutine0:读取到数据:初始数据
读goroutine1:读取到数据:初始数据
读goroutine2:读取到数据:初始数据
读goroutine3:读取到数据:初始数据
读goroutine4:读取到数据:初始数据
写goroutine:开始修改数据
写goroutine:数据修改完成,新数据: 修改后的数据
读goroutine5:读取到数据:修改后的数据
读goroutine6:读取到数据:修改后的数据
读goroutine7:读取到数据:修改后的数据
读goroutine8:读取到数据:修改后的数据
读goroutine9:读取到数据:修改后的数据
所有操作完成
五、Go实战开发:从基础到应用
5.1 简易HTTP服务器(Go标准库实现)
Go标准库net/http内置HTTP服务器,无需依赖Tomcat/Nginx等容器,几行代码即可实现:
示例代码(http_server_demo.go):
package main
import (
"fmt"
"net/http"
"time"
)
// 处理根路径请求
func rootHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, Go HTTP Server! 当前时间:%s", time.Now().Format("2006-01-02 15:04:05"))
}
// 处理用户路径请求(带参数)
func userHandler(w http.ResponseWriter, r *http.Request) {
// 获取URL参数
username := r.URL.Query().Get("username")
if username == "" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "参数错误:username不能为空")
return
}
fmt.Fprintf(w, "你好,%s!", username)
}
func main() {
// 注册路由
http.HandleFunc("/", rootHandler)
http.HandleFunc("/user", userHandler)
// 启动服务器(监听8080端口)
fmt.Println("HTTP服务器启动,监听端口:8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Printf("服务器启动失败:%v\n", err)
}
}
运行方式:
go run http_server_demo.go
测试请求:
-
访问
http://localhost:8080,输出:Hello, Go HTTP Server! 当前时间:2026-01-15 10:00:00 -
访问
http://localhost:8080/user?username=jam,输出:你好,jam! -
访问
http://localhost:8080/user,输出:参数错误:username不能为空
5.2 数据库操作(MySQL)
Go标准库database/sql提供数据库通用接口,需配合驱动(如github.com/go-sql-driver/mysql)操作MySQL:
5.2.1 环境准备
安装MySQL驱动:
go get github.com/go-sql-driver/mysql@latest
5.2.2 示例代码(mysql_demo.go)
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
// User 数据库用户表结构体
type User struct {
ID int
Username string
Age int
Email string
}
func main() {
// 数据库连接信息(替换为自己的配置)
dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
// 打开数据库连接(不会立即建立连接,仅验证参数)
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("打开数据库连接失败:%v", err)
}
defer db.Close() // 程序退出前关闭连接
// 验证连接
err = db.Ping()
if err != nil {
log.Fatalf("数据库连接失败:%v", err)
}
fmt.Println("数据库连接成功")
// 1. 创建表
createTableSQL := `
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
age INT NOT NULL,
email VARCHAR(100)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`
_, err = db.Exec(createTableSQL)
if err != nil {
log.Fatalf("创建表失败:%v", err)
}
fmt.Println("表创建成功(或已存在)")
// 2. 插入数据
insertSQL := "INSERT INTO users (username, age, email) VALUES (?, ?, ?)"
result, err := db.Exec(insertSQL, "jam", 30, "jam@example.com")
if err != nil {
log.Printf("插入数据失败(可能已存在):%v", err)
} else {
id, _ := result.LastInsertId()
fmt.Printf("插入数据成功,ID:%d\n", id)
}
// 3. 查询单条数据
var user User
querySQL := "SELECT id, username, age, email FROM users WHERE username = ?"
err = db.QueryRow(querySQL, "jam").Scan(&user.ID, &user.Username, &user.Age, &user.Email)
if err != nil {
log.Fatalf("查询数据失败:%v", err)
}
fmt.Printf("查询到用户:%+v\n", user)
// 4. 查询多条数据
queryAllSQL := "SELECT id, username, age, email FROM users WHERE age > ?"
rows, err := db.Query(queryAllSQL, 20)
if err != nil {
log.Fatalf("查询多条数据失败:%v", err)
}
defer rows.Close()
var users []User
for rows.Next() {
var u User
err := rows.Scan(&u.ID, &u.Username, &u.Age, &u.Email)
if err != nil {
log.Printf("扫描行失败:%v", err)
continue
}
users = append(users, u)
}
fmt.Printf("查询到的用户列表:%+v\n", users)
// 5. 更新数据
updateSQL := "UPDATE users SET age = ? WHERE username = ?"
_, err = db.Exec(updateSQL, 31, "jam")
if err != nil {
log.Fatalf("更新数据失败:%v", err)
}
fmt.Println("数据更新成功")
// 6. 删除数据(可选)
// deleteSQL := "DELETE FROM users WHERE username = ?"
// _, err = db.Exec(deleteSQL, "jam")
// if err != nil {
// log.Fatalf("删除数据失败:%v", err)
// }
// fmt.Println("数据删除成功")
}
运行结果:
数据库连接成功
表创建成功(或已存在)
插入数据成功,ID:1
查询到用户:{ID:1 Username:jam Age:30 Email:jam@example.com}
查询到的用户列表:[{ID:1 Username:jam Age:30 Email:jam@example.com}]
数据更新成功
5.3 Go与Java核心特性对比(易混淆点区分)
| 特性 | Go语言 | Java语言 |
|---|---|---|
| 并发模型 | goroutine(轻量级)+ channel | 线程(重量级)+ 锁/线程池 |
| 内存管理 | 自动GC(无分代,简单高效) | 分代GC(CMS/G1/ZGC,配置复杂) |
| 编译方式 | 静态编译为单一二进制文件 | 编译为字节码,需JVM解释/即时编译 |
| 面向对象 | 结构体+方法(无类、无继承) | 类+继承+接口 |
| 错误处理 | 多返回值(显式处理错误) | 异常捕获(try-catch) |
| 部署方式 | 单文件部署(无依赖) | 需JRE/JDK,多文件(jar/war) |
| 包管理 | Go Module(go mod) | Maven/Gradle |
六、Go开发最佳实践(权威依据:Go官方《Effective Go》)
-
命名规范 :包名小写且简洁(如
fmt、net/http),结构体名帕斯卡命名(User),函数名帕斯卡命名(导出)/驼峰命名(私有); -
错误处理 :显式处理错误,不要忽略
err,错误信息要具体(包含上下文); -
并发编程:优先使用channel实现goroutine通信,避免共享内存;
-
资源管理 :使用
defer释放资源(如文件、数据库连接),确保资源不泄漏; -
代码简洁:去除冗余代码,Go推崇"简洁即美",避免过度封装;
-
依赖管理 :使用Go Module(
go mod init/go get)管理依赖,指定版本号避免依赖冲突。
总结
-
Go语言的核心优势是高性能、原生并发、极简语法、零依赖部署,适合云原生、微服务、高并发后端开发;
-
goroutine+channel是Go并发编程的核心,遵循"通信共享内存"而非"共享内存通信"的原则;
-
Go的实战开发中,标准库(
net/http、database/sql)足够覆盖大部分场景,无需依赖第三方框架即可快速开发。
掌握Go的核心语法和并发模型后,你可以进一步学习Go的高级特性(如反射、接口进阶、性能优化),或结合框架(如Gin、Beego)进行企业级开发。相比于Java,Go的学习曲线更平缓,且能快速落地到实际项目中,是后端开发者必备的技能之一。