并发与并行:Go语言中的异曲同工

简介: 并发与并行:Go语言中的异曲同工

概述

在 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 实现了两个函数的并发执行。

printNumbersprintLetters 函数交替执行,展现了并发的特性。

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 等待两个并行的任务执行完成。

printNumbersprintLetters 函数被两个 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 程序的关键一步。


目录
相关文章
|
4天前
|
存储 安全 算法
Go语言是如何支持多线程的
【10月更文挑战第21】Go语言是如何支持多线程的
101 72
|
4天前
|
安全 大数据 Go
介绍一下Go语言的并发模型
【10月更文挑战第21】介绍一下Go语言的并发模型
24 14
|
3天前
|
安全 Go 开发者
go语言并发模型
【10月更文挑战第16天】
18 8
|
4天前
|
Java 大数据 Go
Go语言:高效并发的编程新星
【10月更文挑战第21】Go语言:高效并发的编程新星
17 7
|
3天前
|
安全 Java Go
go语言高效切换
【10月更文挑战第16天】
12 5
|
3天前
|
运维 监控 Go
go语言轻量化
【10月更文挑战第16天】
10 3
|
3天前
|
安全 Go 调度
Go语言中的并发编程:解锁高性能程序设计之门####
探索Go语言如何以简洁高效的并发模型,重新定义现代软件开发的边界。本文将深入剖析Goroutines与Channels的工作原理,揭秘它们为何成为实现高并发、高性能应用的关键。跟随我们的旅程,从基础概念到实战技巧,一步步揭开Go并发编程的神秘面纱,让您的代码在多核时代翩翩起舞。 ####
|
4天前
|
Go 调度 开发者
Go语言多线程的优势
【10月更文挑战第15天】
10 4
|
5天前
|
存储 Go 调度
深入理解Go语言的并发模型:goroutine与channel
在这个快速变化的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你穿越Go语言的并发世界,探索goroutine的轻量级特性和channel的同步机制。摘要部分,我们将用一段对话来揭示Go并发模型的魔力,而不是传统的介绍性文字。
|
3月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
120 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库