零基础入门 Go 语言

零基础入门 Go 语言

作为一名长期深耕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):

  1. 数值类型:
    • 整数:int(随系统位数,32/64位)、int8/16/32/64、uint(无符号)、uint8(byte)、uint16/32/64、uintptr;
    • 浮点数:float32、float64(默认);
    • 复数:complex64、complex128。
  2. 布尔类型:bool(true/false,不可用0/1替代)。
  3. 字符串类型:string(UTF-8编码,不可变)。
  4. 派生类型:指针、数组、切片、映射、通道、结构体、接口、函数。

示例代码(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, "[email protected]"}     fmt.Println("u1 =", u1)     // 方式2:指定字段名初始化(推荐,顺序无关)     u2 := User{         ID:       2,         Username: "ken",         Age:      28,         Email:    "[email protected]",     }     fmt.Println("u2 =", u2)     // 方式3:零值初始化,后续赋值     var u3 User     u3.ID = 3     u3.Username = "go_dev"     fmt.Println("u3 =", u3)     // 结构体指针     u4 := &User{4, "pointer", 25, "[email protected]"}     fmt.Println("u4 =", *u4)     // 指针访问字段,Go自动解引用(无需->)     fmt.Println("u4.Username =", u4.Username) } 

运行结果:

u1 = {1 jam 30 [email protected]} u2 = {2 ken 28 [email protected]} u3 = {3 go_dev 0 } u4 = {4 pointer 25 [email protected]} 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):
  1. 主goroutine的生命周期:主goroutine退出后,无论子goroutine是否执行完成,都会被强制终止。上面的示例中time.Sleep(1 * time.Second)是简单的等待方式,但实际开发中不推荐(无法精准控制等待时间)。
  2. goroutine的调度:goroutine由Go运行时(runtime)的M:N调度器管理,将M个goroutine映射到N个操作系统线程,调度开销远低于操作系统线程。
  3. 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, "[email protected]")     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:[email protected]} 查询到的用户列表:[{ID:1 Username:jam Age:30 Email:[email protected]}] 数据更新成功 

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》)

  1. 命名规范:包名小写且简洁(如fmtnet/http),结构体名帕斯卡命名(User),函数名帕斯卡命名(导出)/驼峰命名(私有);
  2. 错误处理:显式处理错误,不要忽略err,错误信息要具体(包含上下文);
  3. 并发编程:优先使用channel实现goroutine通信,避免共享内存;
  4. 资源管理:使用defer释放资源(如文件、数据库连接),确保资源不泄漏;
  5. 代码简洁:去除冗余代码,Go推崇“简洁即美”,避免过度封装;
  6. 依赖管理:使用Go Module(go mod init/go get)管理依赖,指定版本号避免依赖冲突。

总结

  1. Go语言的核心优势是高性能、原生并发、极简语法、零依赖部署,适合云原生、微服务、高并发后端开发;
  2. goroutine+channel是Go并发编程的核心,遵循“通信共享内存”而非“共享内存通信”的原则;
  3. Go的实战开发中,标准库(net/httpdatabase/sql)足够覆盖大部分场景,无需依赖第三方框架即可快速开发。

掌握Go的核心语法和并发模型后,你可以进一步学习Go的高级特性(如反射、接口进阶、性能优化),或结合框架(如Gin、Beego)进行企业级开发。相比于Java,Go的学习曲线更平缓,且能快速落地到实际项目中,是后端开发者必备的技能之一。

Read more

AI大模型落地系列:一文读懂 Eino 的 Embedding

AI大模型落地系列:一文读懂 Eino 的 Embedding

Embedding 使用说明 * 有啥用?! * 他能干嘛? * 它不能直接干嘛? * 总结: * 浅用之法 * 食用之法 * 一、最基本用法:直接调用 `EmbedStrings` * 1. 创建 embedder * 2. 调用 `EmbedStrings` * 3. 向量拿来干嘛 * 二、完整demo * 三、带 Option 怎么用 * 四、在编排中怎么用 * 在 Chain 中使用 * 在 Graph 中使用 * 五、带 Callback 怎么用 * 六、真实场景 * 场景:做知识库问答 * 第一步:把知识库切块 * 第二步:给每个 chunk 生成向量 * 第三步:存起来

By Ne0inhk
9.4k stars!手中就有一整个 AI 团队:agency-agents 深度解析手中就有一整个 AI 团队:agency-agents 深度解析!

9.4k stars!手中就有一整个 AI 团队:agency-agents 深度解析手中就有一整个 AI 团队:agency-agents 深度解析!

手中就有一整个 AI 团队:agency-agents 深度解析 当别人还在反复调试同一个"万能提示词",有人已经在用一支分工明确的 AI 精英团队在干活了。 一、你是不是也有这些痛点? 用 Claude Code 写代码,前一秒在解 Bug,后一秒又要帮你想营销文案,再后一秒还得审查 UI 设计——同一个 AI 上下文频繁切换,结果每件事都做得平平无奇。 通用 AI 的问题在于:它什么都能做,但什么都不够专。 你有没有想过,如果 AI 也能像真实公司一样——前端有前端工程师、设计有 UI 设计师、增长有增长黑客——每个岗位的人用自己深耕多年的方式来工作,结果会有多大不同? agency-agents 就是为了解决这个问题而生的。 二、agency-agents 是什么? agency-agents

By Ne0inhk
人工智能:预训练语言模型与BERT实战应用

人工智能:预训练语言模型与BERT实战应用

人工智能:预训练语言模型与BERT实战应用 1.1 本章学习目标与重点 💡 学习目标:掌握预训练语言模型的核心思想、BERT模型的架构原理,以及基于BERT的文本分类任务实战流程。 💡 学习重点:理解BERT的双向注意力机制与掩码语言模型预训练任务,学会使用Hugging Face Transformers库调用BERT模型并完成微调。 1.2 预训练语言模型的发展历程与核心思想 1.2.1 为什么需要预训练语言模型 💡 传统的自然语言处理模型(如LSTM+词嵌入)存在两个核心痛点:一是需要大量标注数据才能训练出高性能模型,二是模型对语言上下文的理解能力有限。 预训练语言模型的出现解决了这些问题。它的核心思路是先在大规模无标注文本语料上进行预训练,学习通用的语言知识和语义表示,再针对特定任务进行微调。这种“预训练+微调”的范式,极大降低了对标注数据的依赖,同时显著提升了模型在各类NLP任务上的性能。 预训练语言模型的发展可以分为三个阶段: 1. 单向语言模型阶段:以ELMo为代表,通过双向LSTM分别学习正向和反向的语言表示,再拼接得到词向量。但ELMo本质还

By Ne0inhk
Apache IoTDB 部署全指南:AINode 独立部署与 Kubernetes 集群部署

Apache IoTDB 部署全指南:AINode 独立部署与 Kubernetes 集群部署

Apache IoTDB 部署全指南:AINode 独立部署与 Kubernetes 集群部署 IoTDB(Internet of Things Database)作为一款专为时间序列数据设计的数据库,在工业物联网、智能监控等领域应用广泛。随着业务需求的不断发展,仅依靠 ConfigNode 和 DataNode 已难以满足复杂的时序数据机器学习分析需求,AINode 应运而生,为 IoTDB 扩展了机器学习分析能力。同时,在大规模部署场景下,Kubernetes 凭借其强大的容器编排和管理能力,成为 IoTDB 集群部署的理想选择。本文将详细介绍 IoTDB 的 AINode 独立部署和 Kubernetes 集群部署方案,帮助读者轻松完成 IoTDB 相关部署工作。 一、引言 IoTDB(Internet of Things Database)

By Ne0inhk