前言
在计算机领域中,并发 和 并行 是两个常用的概念,它们通常被用于描述计算机程序的执行方式。
并发(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 的基础知识点还是比较好掌握的,加油!
参考链接: