一、引言
并发编程是现代软件开发中的重要技术,它允许程序同时处理多个任务,提高程序的执行效率和响应速度。Go语言作为一种高效、简洁的编程语言,天生支持并发编程,提供了goroutine和channel等强大的并发原语。本笔记将详细介绍Go语言并发编程的基本概念、常用技术以及示例代码,帮助读者更好地理解和掌握Go语言并发编程。
二、基本概念
- 串行(Serial):
-
- 串行执行是指按照顺序一个接一个地执行任务或操作。
- 在串行执行中,每个任务必须在前一个任务完成后才能开始执行。
- 并发(Concurrency):
-
- 并发是指同时处理多个任务或操作,但在时间上可以重叠,不一定是同时执行。
- 操作系统或编程语言通过任务切换或调度机制来实现并发,使得多个任务可以交替执行,并共享系统资源。
- 并行(Parallel):
-
- 并行是指同时执行多个任务或操作,每个任务都在不同的处理器核心或计算单元上独立执行。
- 并行执行要求系统具有多个可同时执行任务的资源,如多核处理器或分布式计算集群。
- 进程(Process):
-
- 进程是操作系统中的一个执行实体,具有独立的内存空间、程序计数器、寄存器、文件句柄等资源。
- 一个进程可以包含多个线程,它们共享进程的资源。
- 线程(Thread):
-
- 线程是进程中的执行单元,是操作系统调度执行的最小单位。
- 一个进程可以包含多个线程,它们共享进程的地址空间和资源。
- 线程之间切换的开销较小,可以实现更高效的并发执行。
- 协程(Coroutine):
-
- 协程是一种轻量级的线程,也被称为用户态线程或纤程。
- 协程由程序自身控制,而不是由操作系统调度。
- 协程可以在同一个线程内切换执行,避免了线程切换的开销。
- 协程通常用于处理高并发、I/O密集型和事件驱动的任务。
三、Go语言并发编程
Go语言通过goroutine和channel提供了强大的并发编程模型。
- goroutine:
-
- goroutine是Go语言中的一种轻量级线程,由Go运行时管理。
- 创建一个goroutine非常简单,只需在函数调用前加上
go关键字。 - 例如:
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello, World!")
}
func main() {
go sayHello() // 创建一个goroutine执行sayHello函数
time.Sleep(time.Second) // 等待一秒,确保goroutine有时间执行
}
-
- 注意:在上面的示例中,
sayHello函数在goroutine中执行,主函数不会等待它完成。因此,我们使用了time.Sleep来确保主函数在退出前有足够的时间让goroutine执行。然而,在实际应用中,更好的做法是使用channel或其他同步机制来协调goroutines之间的执行。
- 注意:在上面的示例中,
- channel:
-
- channel是Go语言中用于在不同goroutine之间进行通信的一种类型安全的管道。
- 你可以使用channel在goroutines之间传递数据,以实现同步和协作。
- 例如:
package main
import (
"fmt"
)
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total // 将计算结果发送到channel
}
func main() {
a := []int{1, 2, 3, 4, 5}
c := make(chan int)
go sum(a[:len(a)/2], c) // 第一个goroutine计算前半部分和
go sum(a[len(a)/2:], c) // 第二个goroutine计算后半部分和
x, y := <-c, <-c // 从channel接收数据
fmt.Println(x, y, x+y) // 输出两部分和以及总和
}
-
- 在这个示例中,我们创建了两个goroutine来计算数组
a的前半部分和后半部分和,并通过channel将结果传递给主函数。主函数从channel中接收这两个结果,并计算它们的总和。
- 在这个示例中,我们创建了两个goroutine来计算数组
四、示例代码
以下是一个综合示例,展示了如何使用goroutine和channel来实现一个简单的并发计算任务:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done() // 标记该goroutine已完成工作
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
time.Sleep(time.Second * time.Duration(rand.Intn(2))) // 模拟工作负载
fmt.Printf("Worker %d finished job %d\n", id, j)
results <- j * 2 // 将处理结果发送到results channel
}
}
func main() {
const numJobs = 5
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// 启动工作goroutines
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// 发送工作到jobs channel
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs) // 关闭jobs channel,表示没有更多的工作要发送
// 等待所有工作goroutines完成
go func() {
wg.Wait()
close(results) // 所有工作完成后关闭results channel
}()
// 接收并打印结果
for result := range results {
fmt.Println("Result:", result)
}
}
在这个示例中,我们创建了三个worker goroutines来处理五个工作项。每个worker从jobs channel中接收一个工作项,处理它(在这里是将工作项乘以2),并将结果发送到results channel。我们使用sync.WaitGroup来等待所有worker goroutines完成工作,并在所有工作完成后关闭results channel。最后,我们从results channel中接收并打印所有结果。
五、总结
Go语言的并发编程模型提供了高效、简洁的方式来处理并发任务。通过goroutine和channel,我们可以轻松地实现任务的并发执行和通信。

361

被折叠的 条评论
为什么被折叠?



