协程并发
1152字约4分钟
2025-02-03
协程并发
/*
golang goroutine原理
GMP
G goroutine协程 用户态
P processor处理器 用户态
M thread线程 内核态
进程,线程,协程的区别是什么?
首先,我们来谈谈进程。进程是操作系统中进行资源分配和调度的基本单位,它拥有自己的独立内存空间和
系统资源。每个进程都有独立的堆和栈,不与其他进程共享。进程间通信需要通过特定的机制,如管道、消
息队列、信号量等。由于进程拥有独立的内存空间,因此其稳定性和安全性相对较高,但同时上下文切换的
开销也较大,因为需要保存和恢复整个进程的状态。
接下来是线程。线程是进程内的一个执行单元,也是CPU调度和分派的基本单位。与进程不同,线程共享进
程的内存空间,包括堆和全局变量。线程之间通信更加高效,因为它们可以直接读写共享内存。线程的上下
文切换开销较小,因为只需要保存和恢复线程的上下文,而不是整个进程的状态。然而,由于多个线程共享
内存空间,因此存在数据竞争和线程安全的问题,需要通过同步和互斥机制来解决。
最后是协程。协程是一种用户态的轻量级线程,其调度完全由用户程序控制,而不需要内核的参与。协程拥
有自己的寄存器上下文和栈,但与其他协程共享堆内存。协程的切换开销非常小,因为只需要保存和恢复协
程的上下文,而无需进行内核级的上下文切换。这使得协程在处理大量并发任务时具有非常高的效率。然而,
协程需要程序员显式地进行调度和管理,相对于线程和进程来说,其编程模型更为复杂。
*/
package main
import (
"fmt"
"time"
)
// 定义一个函数,用于在协程中执行
func sayHello() {
fmt.Println("Hello from goroutine!")
}
// 定义一个带有参数的函数,用于在协程中执行
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
// 创建一个协程,执行 sayHello 函数
go sayHello()
// 创建一个带有参数的协程,执行 greet 函数
go greet("Boge")
// 主线程继续执行其他操作
fmt.Println("Main function continues...")
// 由于协程是并发执行的,因此需要等待一段时间,以确保协程有足够的时间执行
time.Sleep(1 * time.Second)
// 主线程执行完毕,程序退出
fmt.Println("Main function exits.")
}
WaitGroup等待协程
想要等待多个协程完成,我们可以使用 wait group 。
package main
import (
"fmt"
"sync"
"time"
)
func doWork(id int) {
fmt.Println(">任务", id, "开始")
time.Sleep(1 * time.Second)
fmt.Printf("<任务 %d 完成\n", id)
}
func main() {
var wg sync.WaitGroup
taskCount := 5
for i := 0; i < taskCount; i++ { // 启动几个协程,并为其递增 WaitGroup 的计数器
wg.Add(1)
id := i // 避免在每个协程闭包中重复利用相同的 i 值
go func() { // 将 worker 调用包装在一个闭包中,可以确保通知 WaitGroup 此工作线程已完成
defer wg.Done()
doWork(id)
}()
}
wg.Wait() // 阻塞,直到 WaitGroup 计数器恢复为 0; 即所有协程的工作都已经完成
fmt.Println("所有任务完成.")
}
实战:协程并发限制
package main
import (
"fmt"
"sync"
"time"
)
func main() {
jobCounts := 10
channelPoolCounts := 2
BingfaControl(jobCounts, channelPoolCounts, CreateEcs)
}
func CreateEcs(i int, ch <-chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
name := "aliyun"
fmt.Printf("Create %s ECS %d\n", name, i)
time.Sleep(time.Second)
<-ch
}
func BingfaControl(jobCounts, channelPoolCounts int, f func(i int, ch <-chan struct{}, wg *sync.WaitGroup)) {
wg := sync.WaitGroup{}
ch := make(chan struct{}, channelPoolCounts)
start := time.Now()
fmt.Println("====== Start job quickly ======")
for i := 1; i <= jobCounts; i++ {
ch <- struct{}{}
wg.Add(1)
go f(i, ch, &wg)
}
wg.Wait()
fmt.Printf("====== All %d jobs done, cost %s ======\n", jobCounts, time.Since(start))
}
互斥锁
使用一个互斥锁来在 Go 协程间安全的访问数据。
package main
import (
"fmt"
"sync"
"time"
)
type Counter struct {
count int
mu sync.Mutex
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func main() {
now := time.Now()
// 创建一个 Counter 实例
counter := Counter{}
// 创建一个 WaitGroup
var wg sync.WaitGroup
// 设置要启动的并发任务数量
taskCount := 3
// 增加 WaitGroup 的计数
wg.Add(taskCount)
// 启动多个 goroutine 对计数器进行操作
for i := 0; i < taskCount; i++ {
go func() {
// 每个 goroutine 递增计数器的值 10000 次
for j := 0; j < 10000; j++ {
counter.Increment()
}
wg.Done()
}()
}
// 等待所有 goroutine 执行完毕
wg.Wait()
// 输出计数器的值
fmt.Println("计数器的值:", counter.count)
fmt.Println("程序运行时间:", time.Since(now))
}