Go 并发编程:goroutine、channel、select | 青训营笔记

107 阅读2分钟

前言

在计算机领域中,并发并行 是两个常用的概念,它们通常被用于描述计算机程序的执行方式。

并发(concurrency) 指的是程序在单个处理器上同时执行多个任务的能力。这些任务可能会交替执行,但并不一定会在同一时间执行。在并发编程中,通常使用 goroutine 和 channel 来实现多任务的执行。

并行(parallelism) 则指的是在多个处理器上同时执行多个任务的能力。在这种情况下,不同的任务可以在不同的处理器上同时执行,从而加快了整个程序的运行速度。在并行编程中,通常使用线程和锁来实现多任务的执行。

区别在于,并发 是指同时执行多个任务的能力,而 并行 是指同时在多个处理器上执行多个任务的能力。并发的优势在于可以提高程序的响应速度和资源利用率,而并行则可以大大提高程序的计算能力和效率。

goroutine

在 Go 语言中,goroutine 是轻量级线程,可以在单个处理器上同时执行多个任务。与其他语言不同的是,Go 语言的 goroutine 由 Go 语言运行时环境(runtime)管理,而不是由操作系统管理,这使得它们更加轻量级、更易于创建和销毁。

与线程相比,goroutine 的主要区别在于它们的实现方式。传统的线程是由操作系统内核管理的,这意味着线程的创建和销毁等操作都需要系统调用,开销较大。而 goroutine 则是由Go语言运行时环境管理的,它们可以在单个线程上实现多个任务的并发执行,从而避免了线程切换的开销,使得 goroutine 的创建和销毁非常快速。此外,由于 goroutine 由运行时环境管理,因此它们的调度方式也与传统线程不同,这使得 Go 语言的并发编程更加高效和灵活。

goroutine 实现

func main() {  
    // 并发执行  
    go func() {  
        for i := 0; i < 5; i++ {  
            fmt.Println(i)  
        }  
    }()  
    // 等待一秒结束 main 函数  
    time.Sleep(1 * time.Second)  
}

在上述代码中,通过关键字 go 启动一个新的 goroutine。然后使用 time.Sleep 等待 1 秒,因为当主函数结束时,所有未完成的goroutines也会被强制结束,因此在使用goroutines时需要确保它们在主函数结束前已经完成。

// 使用 goroutine 建议使用非全局变量传值,防止多个 goroutine 同时访问全局变量可能会导致竞争条件和数据竞争等问题。

channel

Go 语言提供了 channel 用于在 goroutine 之间传递数据,实现了安全高效的通信机制。这些特性使得 Go 语言非常适合处理并发任务,能够有效地提高程序的响应速度和资源利用率。

channel 分带缓冲与无缓冲。带缓冲的 channel 在创建时可以指定一个缓冲区大小,可以缓存一定数量的数据,而不是每次只能发送或接收一个数据。带缓冲的 channel 具有一些优势:

  • 减少 goroutine 的阻塞时间:当发送和接收数据的 goroutine 之间存在一定的延迟时,使用带缓冲的 channel 可以减少 goroutine 的阻塞时间,提高程序的性能。
  • 减少上下文切换:使用带缓冲的 channel 可以减少发送和接收数据的 goroutine 之间的上下文切换,从而提高程序的性能。
  • 提高程序的灵活性:使用带缓冲的 channel 可以使得程序的不同模块之间更加灵活,可以在一定程度上解耦模块之间的依赖关系。

关于 channel 操作:

  • 创建 channel
  • 发送数据
  • 接收数据
  • 关闭 channel
// <-chan int 表示返回只能读取的 channel  
func ReturnChannel() <-chan int {  
    c := make(chan int)
    // 必须创建一个 goroutine 才能让 channel 发送数据
    go func() {  
        c <- 1  
    }()  
    // 等待 1 秒后返回  
    time.Sleep(1 * time.Second)  
    return c  
}  
  
func main() {  
    c1 := ReturnChannel()  
    fmt.Println(<-c1)  
    // 等待 2 秒结束 main 函数  
    time.Sleep(2 * time.Second)  
}

select

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。 select 随机执行一个可运行的 case。如果没有 case 可运行,那么会执行 default 里的操作,如果没有 default,那么它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

一般 select 用于超时判断:

func Do(c chan int) {  
    // 一秒后发送数据  
    time.Sleep(1 * time.Second)  
    c <- 1  
}  
  
func main() {  
    c1 := make(chan int)  
    go Do(c1)  
    // 声明一个超时时间为 300 毫秒  
    errTime := time.After(300 * time.Millisecond)  
    select {  
    case c := <-c1:  
        fmt.Println(c)  
    case <-errTime:  
        fmt.Println("超时了")  
    }  
    // 等待 2 秒结束 main 函数  
    time.Sleep(2 * time.Second)  
}

结语

对于 goroutine 的基础知识点还是比较好掌握的,加油!

参考链接:

juejin.cn/post/723156…

juejin.cn/post/699313…