并发(Concurrency)和并行(Parallelism)是两个在多任务处理领域中常被讨论的概念,虽然它们在某些情况下看起来相似,但它们描述了两种不同的情景:
一 .并发(Concurrency)
并发指的是系统能够处理多个任务几乎同时进行的能力。这种情况下,任务可能会交替执行,让每个任务都有机会向前进展,但在任何给定的单一时间点上,实际上只有一个任务在执行。这适用于单核或者多核处理器。在单核处理器上,时间片轮转(time slicing)技术可以使得用户感觉到好像有多个任务在同时执行,这就是所谓的"伪并行"。
二 .并行(Parallelism)
并行是指多个处理器或者多核处理器上的多个计算资源同时处理多个任务。每个任务在自己的专用处理器或处理核心上执行,真正意义上的“同时进行”。因此,并行可以显著提高计算速度,因为跨越多个处理器的多个任务可以真正同时执行。
三 .关键区别
处理器使用:并发是关于处理器如何通过切换来管理多个任务的执行,即使这些任务可能不会实际同时执行。而并行则是关于任务的并行执行,多任务同时占用多个处理器。
时间点考量:并发是关于多个任务可以在同一时间间隔内交替执行的特性。如果你有一个单核CPU,你的确可以运行并发程序,但不可以运行并行程序。在其他任何时刻,至多只有一个任务在运行。并行则是指多个计算任务真正同时执行。
硬件与软件:并发往往更多地依赖于软件层面来优化任务的调度和执行流程。并行可能需要硬件的支持(比如多核处理器)。
四. 代码实现
基于Golang 实现并发与并行的实例。
并发demo:启动多个goroutine以并发方式执行任务
package main
import (
"fmt"
"sync"
"time"
)
// 一个示例函数,它模拟耗时任务
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 完成后通知WaitGroup
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // 假设这个工作需要一秒钟
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
numWorkers := 5 // 启动的goroutine数量
for i := 1; i <= numWorkers; i++ {
wg.Add(1) // 通知WaitGroup,有一个新任务被添加
go worker(i, &wg) // 启动goroutine来运行worker
}
wg.Wait() // 等待所有的goroutine完成
}
并行的demo:在多核处理器上,Go运行时会自动地在可用的核心之间调度goroutines来实现并行。以下代码不需要修改也可以并行执行,只要运行环境有多核即可。
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func parallelWorker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Parallel Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Parallel Worker %d done\n", id)
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 设置使用所有可用的CPU核心
var wg sync.WaitGroup
numWorkers := 5 // 启动的goroutine数量
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go parallelWorker(i, &wg)
}
wg.Wait()
}
在main函数里面,runtime.GOMAXPROCS(runtime.NumCPU()) 这行代码设置了程序运行时使用的最大CPU核数为当前机器的CPU核数,这就使得调度器可以在所有CPU核心上调度goroutine,从而达到并行效果。需要注意的是:
由于goroutines的轻量级,你可以创建成千上万个goroutines。sync.WaitGroup 是用来等待一组goroutines执行结束。
默认情况下,Go出于兼容性考虑,可能会将GOMAXPROCS值设置为1,意思是即使是多核CPU,也默认只使用一个核心。不过,从Go 1.5开始,默认值是运行程序的机器的CPU核数,所以通常你无需手动调用runtime.GOMAXPROCS。
正因为Go的设计哲学强调简单和高效,并发和并行是其语言特性中非常重要的部分,通过goroutines和channel,Go使得并发编程变得简单而高效。