概述
在 Go 语言中,经常听到并发(Concurrency)和并行(Parallelism)这两个概念;
它们虽然看似相近,但却有着不同的内涵。
本文将探讨并发与并行的区别,通过实例代码演示,帮助读者深刻理解这两者在 Go 语言中的应用场景及实现方式。
主要内容包括
并发与并行:概念辨析
Go 语言中的并发
Go 语言中的并行
并发与并行的比较
实例演示:任务并发与并行处理
Go 语言中的最佳实践
1. 并发与并行:概念辨析
在计算机开发领域,并发 和 并行 这两个概念经常被混淆使用,但它们有着明确的区别。
简而言之,并发 是指任务在时间上重叠执行,而 并行 则是指任务在空间上同时执行。
2. Go 语言中的并发
2.1
package main import ( "fmt" "time") func main() { go printNumbers() go printLetters() // 等待Goroutine执行完成 time.Sleep(3 * time.Second) } func printNumbers() { for i := 1; i <= 5; i++ { time.Sleep(500 * time.Millisecond) fmt.Printf("%d ", i) }} func printLetters() { for i := 'a'; i <= 'e'; i++ { time.Sleep(300 * time.Millisecond) fmt.Printf("%c ", i) }}
在这个示例中,用 Goroutine 实现了两个函数的并发执行。
printNumbers 和 printLetters 函数交替执行,展现了并发的特性。
2.2
package main import ( "fmt" "time") func main() { c := make(chan string) go sendData(c) go receiveData(c) // 等待Goroutine执行完成 time.Sleep(2 * time.Second) } func sendData(c chan string) { for i := 1; i <= 3; i++ { time.Sleep(500 * time.Millisecond) c <- fmt.Sprintf("Data %d", i) } close(c)} func receiveData(c chan string) { for data := range c { fmt.Println(data) }}
这个示例展示了通过 Channel 进行并发通信的情景。
sendData 函数向 Channel 发送数据,receiveData 函数从 Channel 接收数据,两者同时执行。
2.3
package main import ( "fmt" "time") func main() { ch1 := make(chan string) ch2 := make(chan string) go sendData(ch1) go sendData(ch2) for { select { case data := <-ch1: fmt.Println("From Channel 1:", data) case data := <-ch2: fmt.Println("From Channel 2:", data) case <-time.After(1 * time.Second): fmt.Println("Timeout! No data received.") return } }} func sendData(c chan string) { for i := 1; i <= 3; i++ { time.Sleep(500 * time.Millisecond) c <- fmt.Sprintf("Data %d", i) } close(c)}
在这个示例中,使用 select 语句实现了从多个 Channel 中选择接收数据,以及超时处理的情景。
sendData 函数分别向两个 Channel 发送数据,select 语句等待并选择可用的 Channel 进行数据接收,通过这种方式实现了并发的多路复用。
3. Go 语言中的并行
3.1
package main import ( "fmt" "runtime" "sync" "time") func main() { // 设置使用的CPU核心数 runtime.GOMAXPROCS(2) var wg sync.WaitGroup wg.Add(2) go printNumbers(&wg) go printLetters(&wg) wg.Wait()} func printNumbers(wg *sync.WaitGroup) { defer wg.Done() for i := 1; i <= 5; i++ { time.Sleep(500 * time.Millisecond) fmt.Printf("%d ", i) }} func printLetters(wg *sync.WaitGroup) { defer wg.Done() for i := 'a'; i <= 'e'; i++ { time.Sleep(300 * time.Millisecond) fmt.Printf("%c ", i) }}
在上面示例中,用 runtime.GOMAXPROCS 设置使用的 CPU 核心数,然后通过两个 Goroutine 实现了数字和字母的并行输出。
3.2
package main import ( "fmt" "sync" "time") func main() { var wg sync.WaitGroup wg.Add(2) go printNumbers(&wg) go printLetters(&wg) wg.Wait()} func printNumbers(wg *sync.WaitGroup) { defer wg.Done() for i := 1; i <= 5; i++ { time.Sleep(500 * time.Millisecond) fmt.Printf("%d ", i) }} func printLetters(wg *sync.WaitGroup) { defer wg.Done() for i := 'a'; i <= 'e'; i++ { time.Sleep(300 * time.Millisecond) fmt.Printf("%c ", i) }}
在这个示例中,用 sync.WaitGroup 等待两个并行的任务执行完成。
printNumbers 和 printLetters 函数被两个 Goroutine 同时执行,实现了并行的效果。
4. 并发与并行的比较
4.1 时间轴上的对比
并发(Concurrency):任务在时间上交替执行,通过调度器调度,同一时刻只有一个任务执行。
并行(Parallelism):任务在时间上同时执行,通过多个处理单元(CPU 核心)实现。
4.2 应用场景的对比
并发(Concurrency):适用于 I/O 密集型任务,例如网络请求、文件操作等,通过异步执行提高任务响应性。
并行(Parallelism):适用于 CPU 密集型任务,例如图像处理、科学计算等,通过同时使用多个处理单元提高任务执行速度。
5. 实例演示:任务并发与并行处理
5.1 并发处理任务
package main import ( "fmt" "sync" "time") func main() { var wg sync.WaitGroup wg.Add(3) go processTask("Task 1", &wg) go processTask("Task 2", &wg) go processTask("Task 3", &wg) wg.Wait()} func processTask(name string, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Start processing %s\n", name) time.Sleep(2 * time.Second) fmt.Printf("Finish processing %s\n", name)}
在上面示例中,用三个 Goroutine 并发地处理三个任务。
5.2
package main import ( "fmt" "sync" "time") func main() { // 设置使用的CPU核心数 runtime.GOMAXPROCS(3) var wg sync.WaitGroup wg.Add(3) go processTask("Task 1", &wg) go processTask("Task 2", &wg) go processTask("Task 3", &wg) wg.Wait()} func processTask(name string, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Start processing %s\n", name) time.Sleep(2 * time.Second) fmt.Printf("Finish processing %s\n", name)}
在这个示例中,通过 runtime.GOMAXPROCS 设置使用的 CPU 核心数,使得三个任务可以并行地执行。
6. Go 语言中的最佳实践
6.1 利用并发提高程序性能
在 I/O 密集型任务中使用并发,通过异步执行提高任务的响应性。
使用 Goroutine 和 Channel 进行并发通信,构建高效的并发模型。
6.2 利用并行充分利用多核心
在 CPU 密集型任务中使用并行,通过同时使用多个 CPU 核心提高任务的执行速度。
使用 runtime.GOMAXPROCS 设置并行执行的 CPU 核心数,充分利用计算资源。
7. 总结
通过本文的介绍与实例演示,理解了 Go 语言中并发与并行的区别,以及在不同场景下的应用方式。
并发与并行是 Go 语言中强大的特性,合理使用它们能够提高程序的性能与响应性,使代码更加健壮且高效。
在实际应用中,根据任务的特性选择合适的并发或并行方式,将是编写出优秀 Go 程序的关键一步。