并发(Concurrency)与并行(Parallelism)是两个容易混淆的概念。本文将阐明两者的内涵、实现机制、应用场景、优劣势等方面的区别。
1、并发是什么
并发是一种处理多任务的能力,它意味着程序可以同时处理多段指令流,但是执行顺序未必确定,也不要求同时执行。
例如一个 Web 服务器,可以同时处理请求 A、请求 B,但是执行顺序由系统调度,A 和 B 不一定同时执行。
Go 语言使用 Goroutine 实现并发,Goroutine 是轻量级线程,由 Go 运行时(Runtime)负责调度
func server() { go handleRequestA() go handleRequestB()}
Goroutine 具有启动迅速、资源消耗少的优点,非常适合大规模并发。
2、并行是什么
并行则要求同时执行多道指令,真正做到同时进行,以提高程序执行速度。并行依赖多核 CPU。
Go 语言可以检测 CPU 核数量,将任务并行分配给每个核心执行
func main() { cpu := runtime.NumCPU() var wg sync.WaitGroup wg.Add(cpu) for i := 0; i < cpu; i++ { go func() { defer wg.Done() // 并行执行任务 }() } wg.Wait() // 等待所有任务结束}
这样就实现了真正的并行计算。另外,GPU 带来了更强大的并行计算能力,可用于深度学习等场景。
3、并发与并行的区别
并发与并行存在一些关键区别:
并发强调同时处理多任务,而并行强调同时执行多任务。
并发使用线程、Goroutine 实现,并行使用多核心实现。
并发关注任务处理能力,并行关注执行速度。
简单来说:
并发是处理,并行是执行。
并发可以通过线程等实现,并行依赖多核。
4
// 并发下载多个URLfunc download(urls []string) { ch := make(chan string) for _, url := range urls { go func(url string) { resp := downloadUrl(url) ch <- resp // 返回结果 }(url) } for range urls { <-ch // 获取结果 }}
上面的示例使用 Goroutine 实现了并发下载任务,然后通过 Channel 获取每个下载的结果。
这里强调的是处理多任务的能力,而不是同时执行下载。
5
func calculate(matrix [][]float32) [][]float32 { cpu := runtime.NumCPU() chs := make([]chan []float32, cpu) // 并行计算 for i:= 0; i< cpu; i++ { ch := make(chan []float32) go func(m [][]float32, ch chan []float32) { ch <- parallelCalc(m) }(matrix, ch) chs[i] = ch } // 合并结果 result := make([][]float32, 0) for _, ch := range chs { item := <-ch result = append(result, item) } return result}
这里根据 CPU 数量,将矩阵任务并行分配给每个核心处理,然后合并结果,真正达到了并行计算的效果。
6、并发的优点
相比并行,并发更难实现,但其主要优点是:
更快响应用户交互请求
更好地利用系统资源
代码逻辑组织更合理
显示进度及状态更方便
Go 并发编程可以大大提升服务器响应效率。
7、并行的优点
并行编程的优势在于:
极大提升计算吞吐量
更快完成计算密集型任务
更高的运算效率
使得并行非常适合科学计算、数据分析等场景。
8、总结
并发强调同时处理多任务,并行强调同时执行
并发使用线程等机制,并行利用多核实现
并发偏重任务量,并行注重执行速度
两者在一定程度上可以联用,发挥各自优势
Go 语言很好地支持了并发和并行。理解两者的区别和各自应用场景,可以设计出更合理、高效的程序。