Go语言实战:Goroutine组(Group)在项目中的高效应用‌

并发是 Go 语言的核心特性之一,而在实际项目中,我们往往需要同时启动成百上千个 Goroutine,并对它们进行高效的管理与调度。本文将围绕“Goroutine 组(Group)”展开,详细介绍如何利用 sync.WaitGrouperrgroupcontext、工作池、管道模式等组合方式,在项目中实现高性能、可控的并发逻辑。文章配有代码示例Mermaid 图解详细说明,帮助你快速掌握实战技巧。


目录

  1. 为什么要使用 Goroutine 组?
  2. 基础工具:sync.WaitGroup
    2.1. WaitGroup 基本用法
    2.2. 等待子任务完成场景示例
  3. 错误管理:golang.org/x/sync/errgroup
    3.1. errgroup vs WaitGroup
    3.2. errgroup 并发任务示例
  4. 上下文与取消:context.WithCancel、WithTimeout
    4.1. 用上下文控制一组 Goroutine
    4.2. ctx 取消传播示例
  5. 工人池(Worker Pool)模式
    5.1. Worker Pool 基本原理
    5.2. Worker Pool 代码示例
    5.3. Mermaid 图解:Worker Pool 流程
  6. 并发管道(Pipeline)模式
    6.1. Pipeline 模型简介
    6.2. 多阶段处理示例
    6.3. Mermaid 图解:Pipeline 并发流程
  7. 实战示例:并发文件下载系统
    7.1. 需求描述与设计思路
    7.2. 核心代码解析
    7.3. 流程图示意(Mermaid)
  8. 性能与调优建议
    8.1. 避免过度启动 Goroutine
    8.2. 选择合适的缓冲区大小
    8.3. 减少共享资源竞争
  9. 总结

1. 为什么要使用 Goroutine 组?

在 Go 项目中,我们往往会遇到需要并行处理多个子任务的场景,例如:

  • 同时向多个第三方 API 发起请求,等待全部结果后汇总;
  • 对大量文件或数据记录并发处理,最后统计结果;
  • 在后台启动多个消费者协程,从队列中获取任务并执行。

如果直接 go f() 启动多个 Goroutine,却没有集中管理,就会导致:

  1. 无法知道何时全部完成:主进程提前退出,或后续逻辑无法获取所有结果;
  2. 错误无法汇总:某个子协程发生错误,难以传递给上层进行统一处理;
  3. 取消困难:需要提前中止所有协程时,没有统一的取消机制。

因此,我们引入“Goroutine 组”概念,通过如下手段来高效管理一组并发任务:

  • 使用 sync.WaitGroup 等待所有子任务完成;
  • 使用 errgroup.Group 在出错时能自动取消剩余子任务;
  • 结合 context.Context 实现全局超时或手动取消;
  • 在任务量大时,通过工作池管道控制并发数与数据流。

2. 基础工具:sync.WaitGroup

sync.WaitGroup 是 Go 标准库提供的并发等待工具,用于等待一组 Goroutine 完成。

2.1 WaitGroup 基本用法

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 表示当前 Goroutine 完成
    fmt.Printf("Worker %d 开始\n", id)
    time.Sleep(time.Second) // 模拟工作耗时
    fmt.Printf("Worker %d 完成\n", id)
}

func main() {
    var wg sync.WaitGroup
    numWorkers := 5

    // 启动 5 个 Goroutine
    for i := 1; i <= numWorkers; i++ {
        wg.Add(1) // 增加一个等待计数
        go worker(i, &wg)
    }

    // 等待所有 Goroutine 完成
    wg.Wait()
    fmt.Println("所有 Worker 完成")
}

说明

  • wg.Add(1):对 WaitGroup 计数加 1;
  • defer wg.Done():在 Goroutine 结束前调用 Done(),将计数减 1;
  • wg.Wait():阻塞当前 Goroutine,直到计数降为 0。

2.2 等待子任务完成场景示例

假设有一批 URL,需要并发获取页面并处理,最后才进行汇总:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "sync"
)

func fetchURL(url string, wg *sync.WaitGroup, mu *sync.Mutex, results map[string]int) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        fmt.Printf("获取 %s 失败: %v\n", url, err)
        return
    }
    defer resp.Body.Close()

    body, _ := ioutil.ReadAll(resp.Body)
    mu.Lock()
    results[url] = len(body) // 简单统计内容长度
    mu.Unlock()
}

func main() {
    urls := []string{
        "https://golang.org",
        "https://www.baidu.com",
        "https://www.github.com",
    }
    var wg sync.WaitGroup
    var mu sync.Mutex
    results := make(map[string]int)

    for _, url := range urls {
        wg.Add(1)
        go fetchURL(url, &wg, &mu, results)
    }

    wg.Wait()
    fmt.Println("所有 URL 已抓取,结果如下:")
    for u, length := range results {
        fmt.Printf("%s → %d 字节\n", u, length)
    }
}
  • 通过 sync.Mutex 保护共享的 results,避免并发写冲突;
  • 最后在 wg.Wait() 之后统一输出结果,保证所有子任务完成后再汇总。

3. 错误管理:golang.org/x/sync/errgroup

当并发任务中可能产生错误时,单纯的 sync.WaitGroup 不能将错误传递给主 Goroutine,也无法实现“出错后取消剩余任务”。Go 官方提供的 errgroup 解决了这一需求。

3.1 errgroup vs WaitGroup

  • errgroup.Group 内部集成了 sync.WaitGroup,并增加了错误捕获与取消功能;
  • 一旦某个任务返回非 nil 错误,errgroup 会:

    1. 将该错误保存为全局错误;
    2. 自动取消通过与之关联的 context.Context 生成的子 Context;
    3. 其余挂起任务可通过检查 ctx.Err() 及时退出。

3.2 errgroup 并发任务示例

package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
    "time"
)

// 模拟执行带错误的任务
func doTask(ctx context.Context, id int) error {
    select {
    case <-time.After(time.Duration(id) * 500 * time.Millisecond):
        if id == 2 {
            return fmt.Errorf("任务 %d 失败", id)
        }
        fmt.Printf("任务 %d 完成\n", id)
        return nil
    case <-ctx.Done():
        fmt.Printf("任务 %d 被取消\n", id)
        return ctx.Err()
    }
}

func main() {
    ctx := context.Background()
    g, ctx := errgroup.WithContext(ctx)

    // 启动 3 个并发任务
    for i := 1; i <= 3; i++ {
        i := i // 避免闭包陷阱
        g.Go(func() error {
            return doTask(ctx, i)
        })
    }

    // 主 Goroutine 等待所有任务完成或第一个错误
    if err := g.Wait(); err != nil {
        fmt.Printf("并发任务出错: %v\n", err)
    } else {
        fmt.Println("所有任务成功完成")
    }
}

输出示例

任务 1 完成
并发任务出错: 任务 2 失败
任务 3 被取消
  • 任务 2 在 1s 后失败,errgroup 捕获后取消了任务 3;
  • 最终 g.Wait() 返回第一个错误。

4. 上下文与取消:context.WithCancel、WithTimeout

在复杂的并发场景下,我们往往需要在某个时刻批量取消一组 Goroutine,而不仅仅是等待它们执行完毕。借助 context.Context,可以优雅地实现取消传播。

4.1 用上下文控制一组 Goroutine

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-time.After(300 * time.Millisecond):
            fmt.Printf("Worker %d: 做一次工作\n", id)
        case <-ctx.Done():
            fmt.Printf("Worker %d: 收到取消信号,退出\n", id)
            return
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    // 启动 3 个 Worker
    for i := 1; i <= 3; i++ {
        go worker(ctx, i)
    }

    // 运行 1 秒后取消
    time.Sleep(time.Second)
    fmt.Println("主 Goroutine: 触发取消")
    cancel()

    // 等待一会儿观察输出
    time.Sleep(500 * time.Millisecond)
    fmt.Println("退出")
}

运行结果示例

Worker 1: 做一次工作
Worker 2: 做一次工作
Worker 3: 做一次工作
Worker 1: 做一次工作
Worker 2: 做一次工作
Worker 3: 做一次工作
主 Goroutine: 触发取消
Worker 1: 收到取消信号,退出
Worker 3: 收到取消信号,退出
Worker 2: 收到取消信号,退出
退出
  • 3 个 Worker 在并发执行,当主 Goroutine 调用 cancel() 后,所有 Worker 同时收到取消信号并退出,确保不会遗留僵尸 Goroutine。

4.2 ctx 取消传播示例

结合 WaitGroupcontext,可以在出现某个子任务错误时,取消其他正在运行的子任务:

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

func job(ctx context.Context, id int, wg *sync.WaitGroup, errCh chan<- error) {
    defer wg.Done()
    for {
        select {
        case <-time.After(time.Duration(id) * 300 * time.Millisecond):
            if id == 2 {
                errCh <- fmt.Errorf("job %d 错误", id)
                return
            }
            fmt.Printf("Job %d 完成一次工作\n", id)
        case <-ctx.Done():
            fmt.Printf("Job %d 收到取消,退出\n", id)
            return
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    var wg sync.WaitGroup
    errCh := make(chan error, 1)

    // 启动 3 个并发 job
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go job(ctx, i, &wg, errCh)
    }

    // 等待首个错误或全部完成
    go func() {
        wg.Wait()
        close(errCh)
    }()

    if err, ok := <-errCh; ok {
        fmt.Printf("检测到错误: %v,取消其他任务\n", err)
        cancel()
    }

    // 等待所有 Goroutine 退出
    wg.Wait()
    fmt.Println("主 Goroutine: 所有 job 都已退出")
}
  • errCh 缓冲为 1,用于接收首个错误;
  • 主 Goroutine 在接收到错误后立即 cancel(),其他任务根据 ctx.Done() 退出;
  • 最终 wg.Wait() 确保所有 Goroutine 彻底退出。

5. 工人池(Worker Pool)模式

当任务数量远大于 Goroutine 能承受的并发数时,需要使用工作池模式,限制并发数量,并复用固定数量的 Goroutine 来处理多个任务。

5.1 Worker Pool 基本原理

  1. 固定数量的 Worker(Goroutine):先启动 N 个 Goroutine,作为工作线程池;
  2. 任务队列(Channel):新任务发送到一个任务 Channel;
  3. Worker 取任务执行:每个 Worker 都从任务 Channel 中接收任务,处理完毕后继续循环等待;
  4. 关闭流程:当不再产生新任务时,关闭任务 Channel,Worker 在遍历完 Channel 后自行退出。

Mermaid 图解:Worker Pool 流程

flowchart LR
    subgraph TaskProducer
        A1[产生任务] --> A2[发送到 taskChan]
    end
    subgraph WorkerPool
        direction LR
        W1[Worker1] <---> taskChan
        W2[Worker2] <---> taskChan
        W3[Worker3] <---> taskChan
    end
    subgraph TaskConsumer
        B[Worker 处理完成,写结果或返回]
    end
  • taskChan 代表任务队列,生产者向 Channel 发送任务,多个 Worker 从中并发消费。

5.2 Worker Pool 代码示例

下面用 Go 实现一个简单的 Worker Pool,将一组整数任务并发计算平方并打印。

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, tasks <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for n := range tasks {
        result := n * n
        fmt.Printf("Worker %d 处理任务: %d 的平方 = %d\n", id, n, result)
        time.Sleep(200 * time.Millisecond) // 模拟耗时
        results <- result
    }
    fmt.Printf("Worker %d 退出\n", id)
}

func main() {
    numWorkers := 3
    numbers := []int{2, 3, 4, 5, 6, 7, 8, 9}

    tasks := make(chan int, len(numbers))
    results := make(chan int, len(numbers))

    var wg sync.WaitGroup
    // 启动固定数量的 Worker
    for i := 1; i <= numWorkers; i++ {
        wg.Add(1)
        go worker(i, tasks, results, &wg)
    }

    // 发送任务
    for _, num := range numbers {
        tasks <- num
    }
    close(tasks) // 关闭任务 Channel,表示不会再发送新任务

    // 等待所有 Worker 结束
    wg.Wait()
    close(results)

    fmt.Println("所有 Worker 已退出,结果如下:")
    for res := range results {
        fmt.Println(res)
    }
}

运行流程

  1. 创建 3 个 Worker Goroutine,它们都从 tasks Channel 中读取整数;
  2. 将 8 个整数依次发送到 tasks
  3. close(tasks) 通知 Worker:不再有新任务,Worker 在遍历完 Channel 后退出;
  4. 每个 Worker 计算平方后将结果写入 results Channel;
  5. 等待所有 Worker wg.Wait(),再关闭 results 并遍历输出。

5.3 Mermaid 图解:Worker Pool 流程

flowchart TD
    subgraph Producer[任务生产者]
        A1[生成任务 nums=[2,3,4,...]]
        A1 -->|发送到| TaskChan[taskChan]
    end

    subgraph WorkerPool[Worker 池]
        direction LR
        W1[Worker1] <--> TaskChan
        W2[Worker2] <--> TaskChan
        W3[Worker3] <--> TaskChan
    end

    subgraph Results[结果收集]
        B1[结果 Channel results]
    end

    W1 -->|计算 n^2| B1
    W2 -->|计算 n^2| B1
    W3 -->|计算 n^2| B1

    B1 -->|输出最终结果| Output[打印]

6. 并发管道(Pipeline)模式

管道模式是 Go 并发中的经典模式,将多个处理阶段串联,每个阶段由一组 Goroutine 负责,数据沿着 Channel 从一个阶段流向下一个阶段。

6.1 Pipeline 模型简介

  1. 阶段 1:生成:Producer 产生原始数据,写入 chan1
  2. 阶段 2:处理:一组 Goroutine 从 chan1 中读取数据,进行转换后写入 chan2
  3. 阶段 3:汇总:最后一组 Goroutine 从 chan2 读取结果,进行最终输出或存储。

每个阶段内部也常结合 WaitGrouperrgroup 控制并发数量与错误处理。

6.2 多阶段处理示例

下面示例一个两阶段管道:

  • 阶段 1:生成数字(1\~10);
  • 阶段 2:计算每个数字的平方;
  • 阶段 3:打印结果。
package main

import (
    "fmt"
    "sync"
)

// 生成者,将数字发送到 out Channel
func generator(out chan<- int) {
    for i := 1; i <= 10; i++ {
        out <- i
    }
    close(out)
}

// 计算平方,将输入加以处理后写入 out Channel
func square(in <-chan int, out chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for n := range in {
        out <- n * n
    }
}

// 汇总者,从 in Channel 读取并打印
func printer(in <-chan int, done chan<- struct{}) {
    for sq := range in {
        fmt.Println("平方结果:", sq)
    }
    done <- struct{}{}
}

func main() {
    // 阶段 1 → 阶段 2 → 阶段 3
    ch1 := make(chan int)
    ch2 := make(chan int)
    done := make(chan struct{})

    // 启动生成者
    go generator(ch1)

    // 阶段 2:启动 3 个并发 Goroutine 计算平方
    var wg sync.WaitGroup
    numWorkers := 3
    wg.Add(numWorkers)
    for i := 0; i < numWorkers; i++ {
        go square(ch1, ch2, &wg)
    }

    // 阶段 3:汇总者
    go func() {
        wg.Wait()
        close(ch2)
    }()
    go printer(ch2, done)

    <-done
    fmt.Println("Pipeline 完成")
}
  • generator 产生数字 1\~10,并关闭 ch1
  • square 阶段启动 3 个并发 Goroutine,从 ch1 中不断读取并计算平方,写入 ch2,最后在 wg.Wait() 后关闭 ch2
  • printer 持续从 ch2 中读取并打印,直至 ch2 关闭后,将 done 通知主 Goroutine 退出。

6.3 Mermaid 图解:Pipeline 并发流程

flowchart TD
    subgraph Stage1[生成阶段]
        G[Generator (1~10)] -->|写入| ch1
    end

    subgraph Stage2[计算阶段]
        direction LR
        W1[Worker1] <-- ch1
        W2[Worker2] <-- ch1
        W3[Worker3] <-- ch1
        W1 --> ch2
        W2 --> ch2
        W3 --> ch2
    end

    subgraph Stage3[打印阶段]
        P[Printer] <-- ch2
    end

    P --> Done[主 Goroutine 退出]

7. 实战示例:并发文件下载系统

下面以一个典型的场景做实战:并发下载多个文件,并在下载完成后统一处理。

7.1 需求描述与设计思路

  • 输入一组文件 URL;
  • 使用固定数量的工作池并发下载文件;
  • 在下载完成后,统计所有文件的大小或进行后续处理;
  • 支持“超时”与“出错后取消其余下载”。

设计思路:

  1. 使用 errgroup.Group 创建可取消的上下文;
  2. 搭建一个 Worker Pool,将 URL 写入任务 Channel;
  3. Worker 从 Channel 读取 URL,调用 http.Get 并保存到本地或测量大小;
  4. 出错时通过 Context 取消其余任务;
  5. 最终统计成功下载的文件信息。

7.2 核心代码解析

package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
    "io"
    "net/http"
    "os"
    "path/filepath"
)

const (
    maxWorkers = 5
    timeout    = 30 // 秒
)

// downloadFile 下载 URL 到指定目录,并返回文件大小
func downloadFile(ctx context.Context, url, dir string) (int64, error) {
    // 创建请求并携带上下文
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return 0, err
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return 0, err
    }
    defer resp.Body.Close()

    // 生成本地文件路径
    fileName := filepath.Base(url)
    filePath := filepath.Join(dir, fileName)
    file, err := os.Create(filePath)
    if err != nil {
        return 0, err
    }
    defer file.Close()

    // 写入并统计大小
    n, err := io.Copy(file, resp.Body)
    if err != nil {
        return n, err
    }
    return n, nil
}

func main() {
    urls := []string{
        "https://example.com/file1.zip",
        "https://example.com/file2.zip",
        "https://example.com/file3.zip",
        // ... 更多 URL
    }
    downloadDir := "./downloads"

    // 确保下载目录存在
    os.MkdirAll(downloadDir, 0755)

    // 带超时的 Context
    ctx, cancel := context.WithTimeout(context.Background(), timeout*1e9)
    defer cancel()

    g, ctx := errgroup.WithContext(ctx)

    // 任务队列
    tasks := make(chan string, len(urls))
    // 结果存储 map: url -> 大小
    var mu sync.Mutex
    results := make(map[string]int64)

    // 启动 Worker Pool
    for i := 0; i < maxWorkers; i++ {
        g.Go(func() error {
            for {
                select {
                case <-ctx.Done():
                    return ctx.Err()
                case url, ok := <-tasks:
                    if !ok {
                        return nil // 任务已全部派发
                    }
                    size, err := downloadFile(ctx, url, downloadDir)
                    if err != nil {
                        return err // 发生错误后会取消其他 Goroutine
                    }
                    mu.Lock()
                    results[url] = size
                    mu.Unlock()
                }
            }
        })
    }

    // 派发下载任务
    for _, u := range urls {
        tasks <- u
    }
    close(tasks)

    // 等待所有下载完成或出错/超时
    if err := g.Wait(); err != nil {
        fmt.Printf("下载中出现错误或超时: %v\n", err)
    } else {
        fmt.Println("所有文件下载完成:")
        for u, size := range results {
            fmt.Printf("%s → %d 字节\n", u, size)
        }
    }
}

解析

  1. 带有超时的 Contextcontext.WithTimeout 确保整个下载流程不会无限等待;
  2. errgroup.WithContext:创建带取消功能的 Group,一旦某个 Worker 出错,g.Wait() 会返回错误并触发 Context 取消;
  3. Worker Pool:启动 maxWorkers 个 Goroutine,从 tasks Channel 中获取 URL 并调用 downloadFile
  4. 结果收集:使用 sync.Mutex 保护 results map;
  5. 任务派发与关闭:将所有 URL 写入 tasks 后关闭 Channel,Worker 遍历完后退出。

7.3 流程图示意(Mermaid)

flowchart TD
    subgraph Main Goroutine
        A[准备 URL 列表] --> B[创建带超时 Context 和 errgroup]
        B --> C[启动 maxWorkers 个 Worker]
        C --> D[将所有 URL 写入 tasks Channel 并 close]
        D --> E[g.Wait() 等待所有或第一个错误/超时]
        E -->|成功| F[打印下载结果]
        E -->|错误/超时| G[输出错误信息]
    end

    subgraph Worker Pool
        direction LR
        tasks[(tasks Channel)]
        W1[Worker1] <-- tasks
        W2[Worker2] <-- tasks
        W3[Worker3] <-- tasks
        W4[Worker4] <-- tasks
        W5[Worker5] <-- tasks
        W1 --> results[记录结果]
        W2 --> results
        W3 --> results
        W4 --> results
        W5 --> results
    end

8. 性能与调优建议

在项目中使用 Goroutine 组时,以下经验和技巧可以帮助你获得更好的性能和可控性。

8.1 避免过度启动 Goroutine

  • 硬限制 Goroutine 数量:并发数设置过大,可能导致调度开销和内存压力激增;工作池、并发信号量都是常用手段。
  • 合理估算并发度:根据 CPU 核数、任务 I/O/CPU 特性来设置并发数。例如:I/O 密集型任务可以设置更高并发,CPU 密集型任务应接近 CPU 核数。

8.2 选择合适的缓冲区大小

  • Channel 缓冲大小:根据任务铺垫能力,将任务 Channel 设为足够容量,避免生产者阻塞或数据积压;
  • 结果 Channel:如果有大量结果,适当加大结果 Channel 缓冲,或直接将结果写入并发安全结构。

8.3 减少共享资源竞争

  • 减少锁粒度sync.Mutex 保护共享结构时,应尽量缩小加锁范围;
  • 使用并发安全数据结构:如 sync.Map、原子操作 atomic 包,或分片锁等;
  • 避免热点写冲突:例如多个 Goroutine 同时写一个文件或数据库表时,要考虑分批或加队列处理。

9. 总结

本文围绕“Go 语言实战:Goroutine 组(Group)在项目中的高效应用”展开,从 sync.WaitGrouperrgroupcontext、工作池、管道、到并发文件下载实战示例,详细讲解了常见并发模式与管理方式,并配以代码示例Mermaid 图解,帮助你在实际项目中:

  • 高效启动并管理一组 Goroutine,保证能够等待或取消它们;
  • 在出现错误时及时中断其余任务,避免资源浪费;
  • 结合上下文(context)实现超时与取消传播
  • 使用工作池限制并发度,防止出现大量 Goroutine 导致调度与内存压力;
  • 构建多阶段并发管道,便于分阶段处理任务。

通过上述技巧,你可以在 Go 项目中更加自如地使用并发,实现高效、健壮、易维护的并行处理逻辑。

最后修改于:2025年06月04日 11:03

评论已关闭

推荐阅读

DDPG 模型解析,附Pytorch完整代码
2024年11月24日
DQN 模型解析,附Pytorch完整代码
2024年11月24日
AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日