Go学习第9天:并发编程 + 文件操作 + 正则表达式

Go 语言:并发编程 + 文件操作 + 正则表达式

  • [一、Go 并发编程](#一、Go 并发编程)
    • [1.1 核心基础概念](#1.1 核心基础概念)
    • [1.2 Goroutine 协程](#1.2 Goroutine 协程)
      • [1.2.1 基本语法](#1.2.1 基本语法)
      • [1.2.2 协程等待:sync.WaitGroup](#1.2.2 协程等待:sync.WaitGroup)
      • [1.2.3 踩坑](#1.2.3 踩坑)
    • [1.3 Channel 通道](#1.3 Channel 通道)
      • [1.3.1 分类与基础语法](#1.3.1 分类与基础语法)
        • [1. 无缓冲通道(同步通道)](#1. 无缓冲通道(同步通道))
        • [2. 有缓冲通道](#2. 有缓冲通道)
      • [1.3.2 通道关闭 & 遍历](#1.3.2 通道关闭 & 遍历)
      • [1.3.3 单向通道(只读/只写)](#1.3.3 单向通道(只读/只写))
      • [1.3.4 通道踩坑](#1.3.4 通道踩坑)
    • [1.4 select 多路监听](#1.4 select 多路监听)
    • [1.5 同步锁(解决数据竞争)](#1.5 同步锁(解决数据竞争))
    • [1.6 并发常见问题](#1.6 并发常见问题)
  • 二、文件与目录操作
  • 三、正则表达式
    • [3.1 核心函数](#3.1 核心函数)
    • [3.2 常用正则元字符](#3.2 常用正则元字符)
    • [3.3 实战示例](#3.3 实战示例)
      • [3.3.1 匹配校验(如账号、手机号)](#3.3.1 匹配校验(如账号、手机号))
      • [3.3.2 提取所有匹配内容](#3.3.2 提取所有匹配内容)
      • [3.3.3 正则替换](#3.3.3 正则替换)
      • [3.3.4 字符串分割](#3.3.4 字符串分割)
    • [3.4 正则踩坑](#3.4 正则踩坑)
  • 四、速记表

本文整理 Go 并发编程、文件读写操作、正则表达式 三大实用模块,每个章节包含 语法说明、完整可运行示例、核心规则、高频踩坑 ,适配 VSCode + Go Modules 开发环境,兼顾基础使用与实战场景。

目录

  1. 并发编程(Goroutine、Channel、Select、同步工具)
  2. 文件与目录操作
  3. 正则表达式
  4. 知识点速记

一、Go 并发编程

Go 原生支持高并发,核心组件为 Goroutine(轻量级协程)Channel(通道) ,搭配 selectsync 包实现协程同步、通信与资源安全,区别于传统线程模型,开销极低。

1.1 核心基础概念

  1. Goroutine :Go 运行时管理的轻量级执行单元,由 go 关键字启动,数千上万个协程也可高效调度。
  2. Channel :协程之间专用通信管道,实现数据传递与同步,推荐以通信代替共享内存,规避多线程数据竞争。
  3. GMP 调度模型:Go 底层调度机制(G=协程、M=系统线程、P=逻辑处理器),开发者无需手动管理。
  4. 常见并发问题:死锁数据竞争

1.2 Goroutine 协程

1.2.1 基本语法

使用 go 函数名(参数) 即可启动一个新协程,主协程(main)退出后,所有子协程会直接终止。

go 复制代码
package (main)
import "fmt"
import "time"

// 普通函数
func sayHello() {
    for i := 0; i < 5; i++ {
        fmt.Println("Hello")
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go sayHello() // 启动子协程
    // 主协程循环
    for i := 0; i < 5; i++ {
        fmt.Println("Main")
        time.Sleep(100 * time.Millisecond)
    }
}

运行特点:输出顺序随机,两个协程交替执行。

1.2.2 协程等待:sync.WaitGroup

单纯 go 启动协程无法等待执行完毕,sync.WaitGroup 专门用于批量等待多个协程结束

核心方法:

  • Add(n):设置/增加等待计数器;
  • Done():协程执行完毕,计数器减 1;
  • Wait():阻塞主协程,直到计数器为 0。

示例

go 复制代码
package main
import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 函数结束自动计数减1
    fmt.Printf("协程 %d 执行完毕\n", id)
}

func main() {
    var wg sync.WaitGroup
    // 启动3个协程
    for i := 1; i <= 3; i++ {
        wg.Add(1) // 计数器+1
        go worker(i, &wg)
    }
    wg.Wait() // 等待所有协程完成
    fmt.Println("所有协程执行结束")
}

1.2.3 踩坑

  1. 忘记调用 Add()/Done(),导致 Wait() 永久阻塞或提前退出;
  2. WaitGroup 必须传指针,值传递会拷贝对象,计数失效;
  3. 主函数提前退出,子协程会被强制终止。

1.3 Channel 通道

通道是协程间的数据载体,使用 chan 关键字声明,make 创建,通过 <- 完成收发。

1.3.1 分类与基础语法

1. 无缓冲通道(同步通道)

创建:ch := make(chan 数据类型)

规则:发送方、接收方必须同时就绪,否则互相阻塞。

go 复制代码
package main
import "fmt"

func sum(s []int, c chan int) {
    total := 0
    for _, v := range s {
        total += v
    }
    c <- total // 向通道发送数据
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}
    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // 从通道接收数据
    fmt.Println(x + y)
}
2. 有缓冲通道

创建:ch := make(chan 类型, 缓冲区大小)

规则:缓冲区未满时发送不阻塞;缓冲区满/无数据时阻塞。

go 复制代码
package main
import "fmt"

func main() {
    // 缓冲区大小为2
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

1.3.2 通道关闭 & 遍历

  • close(ch):关闭通道,关闭后不可再发送数据,但仍可接收;
  • for range:遍历通道,通道关闭后循环自动退出(通道不关闭会永久阻塞)。

示例

go 复制代码
package main
import "fmt"

func fib(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c) // 关闭通道
}

func main() {
    c := make(chan int, 10)
    go fib(10, c)
    // 遍历通道
    for val := range c {
        fmt.Println(val)
    }
}

1.3.3 单向通道(只读/只写)

限制通道方向,提升代码安全性:

  • chan<- int:只写通道(仅发送数据);
  • <-chan int:只读通道(仅接收数据)。

1.3.4 通道踩坑

  1. 已关闭通道发送数据 → 运行 panic;
  2. 接收无数据、无缓冲且无发送方的通道 → 死锁;
  3. 重复关闭通道 → panic;
  4. 遍历未关闭的通道 → 永久阻塞。

1.4 select 多路监听

select 用于同时监听多个通道 ,语法类似 switch

  1. 多个 case 同时就绪:随机选一个执行
  2. 所有 case 未就绪:无 default 则阻塞,有 default 直接执行默认分支。

示例

go 复制代码
package main
import "fmt"

func fib(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit: // 接收退出信号
            fmt.Println("协程退出")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0 // 发送退出信号
    }()
    fib(c, quit)
}

1.5 同步锁(解决数据竞争)

多个协程同时读写同一共享变量会产生数据竞争 ,使用 sync.Mutex(互斥锁)、sync.RWMutex(读写锁)保护资源。

  • Lock():加锁,进入临界区;
  • Unlock():解锁,释放资源。
go 复制代码
package main
import (
    "fmt"
    "sync"
)

var count int
var mu sync.Mutex

func add(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()   // 加锁
    count++
    mu.Unlock() // 解锁
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go add(&wg)
    }
    wg.Wait()
    fmt.Println(count)
}

1.6 并发常见问题

  1. 死锁 :所有协程互相等待,无任何协程继续执行;
    诱因:通道收发不匹配、锁未释放、循环等待。
  2. 数据竞争 :多协程同时读写共享变量;
    解决方案:使用通道通信 或 互斥锁。

二、文件与目录操作

Go 依靠标准库完成文件/目录读写、创建、删除、遍历等操作,核心库:

  • os:底层文件/目录操作(主流);
  • bufio:带缓冲读写,优化大文件 I/O 性能;
  • io:通用读写接口;
  • path/filepath:跨平台路径处理;
  • ioutilGo1.16+ 已弃用 ,功能迁移至 os/io

2.1 常用函数速查表

库/函数 作用
os.Create(name) 创建文件,存在则清空内容
os.Open(name) 只读打开文件
os.OpenFile() 自定义模式(读写/追加)打开文件
os.ReadFile(name) 一次性读取整个文件(小文件首选)
os.WriteFile() 一次性写入文件(覆盖)
os.Remove() 删除文件/空目录
os.Mkdir() 创建单层目录
os.MkdirAll() 递归创建多级目录
bufio.Scanner 逐行读取(大文件)
bufio.Writer 缓冲写入,提升效率
filepath.Join() 跨平台拼接路径

2.2 基础文件操作

2.2.1 创建 & 打开 & 关闭文件

规则:文件打开后必须关闭 ,推荐搭配 defer 延迟关闭,避免文件句柄泄漏。

go 复制代码
package main
import (
    "log"
    "os"
)

func main() {
    // 创建文件
    file, err := os.Create("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close() // 函数退出前自动关闭文件
    log.Println("文件创建成功")

    // 只读打开文件
    f2, err := os.Open("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer f2.Close()
}

2.2.2 读取文件

方式1:一次性读取(小文件)
go 复制代码
package main
import (
    "fmt"
    "os"
)

func main() {
    data, err := os.ReadFile("test.txt")
    if err != nil {
        fmt.Println("读取失败:", err)
        return
    }
    fmt.Println(string(data)) // 字节切片转字符串
}
方式2:逐行读取(大文件,bufio)
go 复制代码
package main
import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text()) // 逐行输出
    }
    // 检查读取错误
    if err := scanner.Err(); err != nil {
        fmt.Println("读取异常:", err)
    }
}

2.2.3 写入文件

方式1:整体覆盖写入
go 复制代码
package main
import (
    "fmt"
    "os"
)

func main() {
    content := []byte("Hello Go 文件操作")
    err := os.WriteFile("test.txt", content, 0644)
    if err != nil {
        fmt.Println(err)
    }
}
方式2:追加写入(os.O_APPEND
go 复制代码
package main
import (
    "fmt"
    "os"
)

func main() {
    // 追加+只写,文件权限 0644
    file, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close()

    _, _ = file.WriteString("\n追加新内容")
}
方式3:缓冲写入(大批量数据)
go 复制代码
package main
import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("write.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close()

    writer := bufio.NewWriter(file)
    _, _ = writer.WriteString("缓冲写入内容\n")
    writer.Flush() // 刷新缓冲区,数据落地磁盘
}

2.3 目录操作

go 复制代码
package main
import (
    "log"
    "os"
)

func main() {
    // 1. 创建单层目录
    err := os.Mkdir("mydir", 0755)
    if err != nil {
        log.Println(err)
    }

    // 2. 递归创建多级目录
    err = os.MkdirAll("a/b/c", 0755)
    if err != nil {
        log.Println(err)
    }

    // 3. 读取目录下文件/子目录
    entries, err := os.ReadDir(".")
    if err != nil {
        log.Fatal(err)
    }
    for _, entry := range entries {
        fmt.Println(entry.Name(), entry.IsDir())
    }

    // 4. 删除空目录/文件
    _ = os.Remove("mydir")
    // 递归删除目录及所有内容
    _ = os.RemoveAll("a")
}

2.4 辅助操作

  1. 判断文件是否存在
go 复制代码
if _, err := os.Stat("test.txt"); os.IsNotExist(err) {
    fmt.Println("文件不存在")
}
  1. 文件复制 :使用 io.Copy
  2. 跨平台路径 :统一使用 filepath.Join("dir", "file.txt") 拼接路径,兼容 Windows / Linux / Mac。

2.5 文件操作踩坑

  1. 打开文件后忘记 Close,长期运行导致文件句柄泄漏;
  2. 追加文件未使用 os.O_APPEND,直接覆盖原有内容;
  3. 缓冲写入后未执行 Flush(),数据留在缓冲区,磁盘无内容;
  4. 权限数字 0644/0755 必须以 0 开头,写法错误导致权限异常;
  5. 区分 os.Remove(仅删空目录/文件)和 os.RemoveAll(递归删除)。

三、正则表达式

Go 使用标准库 regexp 实现正则,用于字符串匹配、查找、替换、分割,适合表单校验、文本提取等场景。

3.1 核心函数

  1. regexp.MustCompile(正则):编译正则,出错直接 panic(常用);
  2. regexp.Compile(正则):编译正则,返回错误(严谨场景);
  3. MatchString():判断字符串是否完全匹配规则;
  4. FindString():获取第一个匹配结果;
  5. FindAllString():获取所有匹配结果;
  6. ReplaceAllString():正则替换;
  7. Split():按正则分割字符串。

3.2 常用正则元字符

符号 作用
. 匹配任意单个字符(不含换行)
\d 数字 [0-9]
\w 字母、数字、下划线
\s 空白符(空格/制表符/换行)
+ 前面字符出现 1 次及以上
* 前面字符出现 0 次及以上
? 前面字符出现 0 或 1 次
^ 字符串开头
$ 字符串结尾
[] 匹配括号内任意字符

Go 中推荐使用反引号 ````` 包裹正则,避免 \ 二次转义。

3.3 实战示例

3.3.1 匹配校验(如账号、手机号)

go 复制代码
package main
import (
    "fmt"
    "regexp"
)

func main() {
    // 匹配纯字母+数字
    reg := regexp.MustCompile(`^[a-zA-Z0-9]+$`)
    str1 := "Go123"
    str2 := "Go@123"
    fmt.Println(reg.MatchString(str1)) // true
    fmt.Println(reg.MatchString(str2)) // false
}

3.3.2 提取所有匹配内容

go 复制代码
package main
import (
    "fmt"
    "regexp"
)

func main() {
    // 提取所有数字
    reg := regexp.MustCompile(`\d+`)
    str := "苹果3个,香蕉5斤"
    res := reg.FindAllString(str, -1)
    fmt.Println(res) // [3 5]
}

3.3.3 正则替换

go 复制代码
package main
import (
    "fmt"
    "regexp"
)

func main() {
    // 把多个空格替换为单个空格
    reg := regexp.MustCompile(`\s+`)
    str := "Hello    World  Go"
    newStr := reg.ReplaceAllString(str, " ")
    fmt.Println(newStr) // Hello World Go
}

3.3.4 字符串分割

go 复制代码
package main
import (
    "fmt"
    "regexp"
)

func main() {
    reg := regexp.MustCompile(`,`)
    str := "apple,banana,orange"
    parts := reg.Split(str, -1)
    fmt.Println(parts)
}

3.4 正则踩坑

  1. 普通双引号内 \ 需要转义为 \\,优先使用 ````` 反引号;
  2. 正则编译建议全局一次执行,不要循环内反复 Compile(性能差);
  3. MustCompile 正则语法错误会直接崩溃,正式环境优先用 Compile + 错误判断;
  4. 复杂正则会大幅降低性能,大数据场景尽量改用普通字符串处理。

四、速记表

模块 核心要点 高频坑点
并发 go启动协程;channel协程通信;select监听多通道;WaitGroup等待;Mutex加锁 通道死锁、协程计数错误、锁忘记释放
文件操作 os为主,bufio做缓冲;defer关闭文件;0644权限;Append追加 句柄泄漏、缓冲未刷新、覆盖/追加混淆
正则 regexp包;````` 原生字符串;Compile/MustCompile 转义符问题、循环编译正则、复杂正则性能低
相关推荐
有Li1 小时前
PTCMIL:基于提示 token 聚类的全切片图像多实例学习分析文献速递/多模态医学影像最新进展
论文阅读·学习·数据挖掘·聚类·文献·医学生
憧憬成为web高手2 小时前
l33t-hoster
学习·web安全·网络安全
Dick5072 小时前
ROS2 常用命令表
人工智能·学习·算法·机器人
qeen872 小时前
【Linux】Linux简单介绍与基本指令(上)
linux·运维·服务器·学习
JCGKS2 小时前
Go `init` 函数:包初始化顺序到底是怎样的
golang·init·init执行顺序
.千余2 小时前
【C++】模板进阶全解:非类型参数|全特化|偏特化|分离编译完全指南
开发语言·c++·笔记·学习·其他
自传.2 小时前
尚硅谷 Vibe Coding|第二章 AI编程工具生态 学习笔记
笔记·学习·ai编程·尚硅谷·vibe coding
库奇噜啦呼3 小时前
【iOS】RunLoop学习
学习·ios
AI棒棒牛3 小时前
第 03 讲《监督学习:数据、标签、Loss与训练循环》
人工智能·学习·yolo·目标检测·yolo26