GO异步学习

GO异步学习

文章目录

在 Go 语言中,实现异步设计通常涉及到协程(goroutines)和通道(channels)。Go 语言的并发编程模型是其一大亮点,使得异步编程变得相对简单和直观。

了解异步

1. 使用 Goroutines

Goroutine 是 Go 语言实现并发的基本单元。它们是轻量级的线程,由 Go 运行时管理。你可以用 go 关键字启动一个 goroutine。

go 复制代码
package main

import (
    "fmt"
    "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Println(i)
        time.Sleep(time.Second)
    }
}

func main() {
    go printNumbers() // 启动 goroutine
    // 主程序继续执行
    fmt.Println("Main function is running concurrently")
    time.Sleep(6 * time.Second) // 确保主程序在 goroutine 完成之前退出
}
2. 使用 Channels

Channels 是用来在 goroutines 之间传递数据的管道。它们提供了同步的机制,确保了数据在并发环境下的安全性。

go 复制代码
package main

import (
    "fmt"
    "time"
)

func sendData(ch chan<- string) {
    time.Sleep(2 * time.Second)
    ch <- "Data from goroutine"
}

func main() {
    ch := make(chan string)
    
    go sendData(ch)
    
    data := <-ch
    fmt.Println(data)
}
3. 选择语句(Select Statement)

select 语句允许你等待多个 channel 操作。它可以用来处理超时和多个 channel 的读取操作。

go 复制代码
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(2 * time.Second)
        ch1 <- "Result from channel 1"
    }()
    
    go func() {
        time.Sleep(1 * time.Second)
        ch2 <- "Result from channel 2"
    }()
    
    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    case <-time.After(3 * time.Second):
        fmt.Println("Timeout")
    }
}
4. 错误处理与同步

在并发编程中,错误处理和同步是很重要的。可以使用 sync 包中的 WaitGroup 来等待多个 goroutine 完成。

go 复制代码
package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d started\n", id)
    // 模拟工作
    time.Sleep(2 * time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }
    wg.Wait()
    fmt.Println("All workers completed")
}

功能特点

Goroutines 和 Channels

  • Goroutines : 轻量级线程,由 Go 运行时管理。通过 go 关键字启动,可以非常方便地实现并发执行。
  • Channels: 用于在 goroutines 之间传递数据,提供了同步机制,避免了数据竞争。
  • Select: 用于处理多个 channel 操作的选择,支持超时处理。

优点:

  • 简洁性: 启动并管理 goroutines 非常简单。
  • 高效性: Goroutines 比系统线程更轻量,占用的内存更少。
  • 内置同步: Channels 提供了内置的同步机制,避免了传统并发编程中的许多问题。

缺点:

  • 学习曲线: 对于初学者,理解 channels 和 goroutines 的工作原理可能需要一些时间。
  • 调试复杂性: 多 goroutine 程序的调试可能比较复杂,尤其是当程序涉及到复杂的同步和通信时。

Goroutines 是 Go 语言的核心特性之一,用于实现并发编程。它们是轻量级的执行线程,由 Go 运行时管理,使得并发编程变得更加高效和简单。以下是关于 goroutines 的使用和场景的详细介绍:

Goroutine基本使用
启动 Goroutine

使用 go 关键字可以启动一个新的 goroutine。goroutine 是并发的,它们可以与主程序或者其他 goroutine 并行执行。

go 复制代码
package main

import (
    "fmt"
    "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Println(i)
        time.Sleep(time.Second)
    }
}

func main() {
    go printNumbers() // 启动一个新的 goroutine
    time.Sleep(6 * time.Second) // 等待 goroutine 完成
}
使用场景
I/O 密集型任务

Goroutines 非常适合处理 I/O 密集型任务,比如处理网络请求、文件操作或数据库查询。因为 Go 运行时对 I/O 操作进行了优化,goroutines 可以在 I/O 操作期间进行调度,极大提高了程序的并发性。

go 复制代码
package main

import (
    "fmt"
    "net/http"
    "time"
)

func fetchURL(url string) {
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("Error fetching URL:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("Fetched URL:", url)
}

func main() {
    urls := []string{"http://example.com", "http://golang.org", "http://google.com"}

    for _, url := range urls {
        go fetchURL(url) // 启动一个 goroutine 来处理每个 URL
    }

    time.Sleep(5 * time.Second) // 等待所有 goroutines 完成
}
并行计算

在需要执行大量计算任务的情况下,可以使用 goroutines 将计算任务分配到多个 goroutines 中,从而提高计算效率。

go 复制代码
package main

import (
    "fmt"
    "sync"
)

func calculateSquare(n int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Square of %d is %d\n", n, n*n)
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    var wg sync.WaitGroup

    for _, num := range numbers {
        wg.Add(1)
        go calculateSquare(num, &wg) // 启动一个 goroutine 来计算平方
    }

    wg.Wait() // 等待所有 goroutines 完成
}
处理并发请求

在 Web 服务器或者微服务架构中,goroutines 可以处理多个并发请求。例如,使用 goroutines 处理每个客户端的请求,提高服务器的吞吐量。

go 复制代码
package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, world!")
}

func main() {
    http.HandleFunc("/", handler)
    fmt.Println("Starting server on port 8080...")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Println("Error starting server:", err)
    }
}
注意事项
资源管理

尽管 goroutines 是轻量级的,但不应滥用它们。启动过多的 goroutines 可能会消耗大量的内存和系统资源。确保你在设计时考虑到资源管理和调度。

同步和数据竞争

在多个 goroutines 之间共享数据时,要注意同步问题。Go 提供了 sync 包中的 MutexRWMutex 来解决数据竞争问题。正确使用 channels 也是避免数据竞争的一种方式。

错误处理

在 goroutines 中处理错误时,考虑使用 channels 或其他机制来收集错误信息并进行处理,以确保程序的健壮性。

go 复制代码
package main

import (
    "fmt"
    "sync"
)

func performTask(id int, ch chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    // 模拟任务
    result := fmt.Sprintf("Task %d completed", id)
    ch <- result
}

func main() {
    var wg sync.WaitGroup
    ch := make(chan string, 5)

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go performTask(i, ch, &wg)
    }

    go func() {
        wg.Wait()
        close(ch)
    }()

    for msg := range ch {
        fmt.Println(msg)
    }
}
总结

Goroutines 是 Go 语言实现并发编程的核心特性,具有启动简单、性能高效等优点。它们适用于各种并发场景,包括 I/O 密集型任务、并行计算和处理并发请求等。在使用 goroutines 时,注意资源管理、同步和错误处理,能够帮助你更好地利用 Go 的并发能力。

在 Go 语言中,Channels(通道)是 goroutines 之间进行通信和同步的主要工具。它们提供了一种安全的方式来在并发环境中传递数据,同时避免了数据竞争和其他并发问题。以下是关于 Channels 的使用和典型场景的详细介绍:

Channel基本使用
创建和使用 Channel

你可以使用 make 函数创建一个 channel,并指定其数据类型。通过 <- 操作符可以从 channel 中发送和接收数据。

go 复制代码
package main

import "fmt"

func main() {
    ch := make(chan int) // 创建一个 int 类型的 channel

    // 启动一个 goroutine 发送数据
    go func() {
        ch <- 42 // 发送数据到 channel
    }()

    // 从 channel 接收数据
    value := <-ch
    fmt.Println(value) // 输出: 42
}
Channel 的关闭

关闭 channel 是一种通知接收方没有更多数据要发送的方式。可以使用 close 函数来关闭 channel。关闭的 channel 不能再发送数据,但可以继续接收数据,直到接收完所有数据。

go 复制代码
package main

import "fmt"

func main() {
    ch := make(chan int)

    // 启动一个 goroutine 发送数据并关闭 channel
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
        }
        close(ch) // 关闭 channel
    }()

    // 从 channel 接收数据
    for value := range ch {
        fmt.Println(value) // 输出: 1 2 3 4 5
    }
}
使用场景
同步和协调

Channels 可以用来协调多个 goroutines,确保它们在继续执行之前完成某些操作。比如,可以使用 channel 来实现工作池(Worker Pool)模式。

go 复制代码
package main

import (
    "fmt"
    "sync"
)

func worker(id int, ch <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for task := range ch {
        fmt.Printf("Worker %d processing task %d\n", id, task)
    }
}

func main() {
    const numWorkers = 3
    tasks := []int{1, 2, 3, 4, 5}
    ch := make(chan int)
    var wg sync.WaitGroup

    // 启动 worker goroutines
    for i := 1; i <= numWorkers; i++ {
        wg.Add(1)
        go worker(i, ch, &wg)
    }

    // 发送任务到 channel
    go func() {
        for _, task := range tasks {
            ch <- task
        }
        close(ch) // 关闭 channel,通知所有 worker 完成
    }()

    wg.Wait() // 等待所有 worker 完成
}
管道(Pipeline)

Channels 也可以用于实现数据处理管道,数据在管道中经过一系列的处理步骤。

go 复制代码
package main

import "fmt"

func generateNumbers() <-chan int {
    ch := make(chan int)
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
        }
        close(ch)
    }()
    return ch
}

func squareNumbers(input <-chan int) <-chan int {
    ch := make(chan int)
    go func() {
        for num := range input {
            ch <- num * num
        }
        close(ch)
    }()
    return ch
}

func main() {
    numbers := generateNumbers()
    squares := squareNumbers(numbers)

    for square := range squares {
        fmt.Println(square) // 输出: 1 4 9 16 25
    }
}
工作分配

使用 channel 进行工作分配,例如在一个并发环境中分配任务到多个 worker goroutine 中进行处理。

go 复制代码
package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        results <- job * job // 计算平方并发送到 results channel
    }
}

func main() {
    const numJobs = 5
    const numWorkers = 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    var wg sync.WaitGroup

    // 启动 worker goroutines
    for i := 1; i <= numWorkers; i++ {
        wg.Add(1)
        go worker(i, jobs, results, &wg)
    }

    // 发送任务到 channel
    for i := 1; i <= numJobs; i++ {
        jobs <- i
    }
    close(jobs) // 关闭 jobs channel

    // 等待所有 worker 完成
    wg.Wait()
    close(results) // 关闭 results channel

    // 处理结果
    for result := range results {
        fmt.Println(result)
    }
}
高级使用
Buffered Channels

Buffered channels 具有缓冲区,可以在发送操作不会阻塞的情况下存储一定数量的数据。可以通过 make(chan Type, capacity) 来创建带缓冲的 channel。

go 复制代码
package main

import "fmt"

func main() {
    ch := make(chan int, 2) // 创建一个缓冲区大小为 2 的 channel

    ch <- 1
    ch <- 2
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}
Select 语句

select 语句用于处理多个 channel 操作。可以在多个 channel 中进行选择,支持超时处理。

go 复制代码
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(2 * time.Second)
        ch1 <- "result from ch1"
    }()

    go func() {
        time.Sleep(1 * time.Second)
        ch2 <- "result from ch2"
    }()

    select {
    case res1 := <-ch1:
        fmt.Println(res1)
    case res2 := <-ch2:
        fmt.Println(res2)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout")
    }
}
总结
  • 通信: Channels 提供了在 goroutines 之间安全地传递数据的机制。
  • 同步: 可以用 channels 来同步 goroutines,确保它们完成某些操作。
  • 管道: 通过 channels 实现数据处理管道,简化数据流处理。
  • 工作池: 利用 channels 实现工作池模式,提高处理效率。
  • 缓冲和选择 : 使用缓冲 channels 和 select 语句来提高灵活性和处理并发场景。

Channels 是 Go 语言并发编程的重要组成部分,掌握它们的使用能够帮助你设计出高效、可靠的并发程序。

相关推荐
梁梁梁梁较瘦14 小时前
边界检查消除(BCE,Bound Check Elimination)
go
梁梁梁梁较瘦15 小时前
指针
go
梁梁梁梁较瘦15 小时前
内存申请
go
半枫荷15 小时前
七、Go语法基础(数组和切片)
go
梁梁梁梁较瘦1 天前
Go工具链
go
半枫荷2 天前
六、Go语法基础(条件控制和循环控制)
go
半枫荷3 天前
五、Go语法基础(输入和输出)
go
小王在努力看博客3 天前
CMS配合闲时同步队列,这……
go
Anthony_49263 天前
逻辑清晰地梳理Golang Context
后端·go
Dobby_054 天前
【Go】C++ 转 Go 第(二)天:变量、常量、函数与init函数
vscode·golang·go