调度器实现并发任务和收集
1 简介
本文可能会过期,目的是旨在阐明在 Go 运行时编程与编写普通 Go编写普通 Go 的区别。

本文着重于普遍的概念,而不是特定接口的细节。
2 调度器结构
调度器管理三种类型的资源,它们充斥于运行时间。
即使你不在调度器上工作,了解这些也是很重要的。
你不在调度器上工作,也要了解这些。
- Gs, Ms, Ps
一个 "G "就是一个goroutine。它由g类型表示。
一个goroutine退出时,它的g对象被返回到一个空闲的g池中,并且可以在以后被其他goroutine重新使用。
M是一个操作系统线程,可以执行用户Go代码、运行时代码,系统调用,或处于空闲状态。
它由m类型表示。一个线程可以有可以有任何数量的Ms,因为任何数量的线程都可能在系统调用中被阻塞。
在系统调用中被阻塞。
最后,"P "代表执行用户go代码所需的资源,如调度器和内存。
代码所需的资源,如调度器和内存分配器状态。
它的代表是由类型p表示。正好有GOMAXPROCSP。
一个P可以被认为是就像操作系统调度器中的一个CPU,p类型的内容就像每个CPU的状态。
这是一个很好的例子,可以把需要分片来提高效率的状态放在这里。
分散以提高效率,但不需要按线程或每程序。
调度器的工作是匹配一个G(要执行的代码)、一个M(在哪里执行),以及一个P(执行的权利和资源)。
它的权利和资源,当一个M停止执行用户的Go代码时,例如通过进入一个系统调用而停止执行用户go代码时,它会将其P返回到闲置的P池中。
为了恢复执行用户代码,例如从系统调用返回时,它必须从空闲的P池中获取一个P。
必须从闲置的P池中获取一个P。所有g、m和p对象都是堆分配的,但永远不会被释放。
所以它们的内存保持类型稳定。runtime运行时可以避免在调度器的深处出现写屏障。
3 调度器示例任务分发与结果汇总(worker pool)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
results <- job * 2
fmt.Printf("Worker %d processed job %d\n", id, job)
}
}
func main() {
jobs := make(chan int, 5)
results := make(chan int, 5)
var wg sync.WaitGroup
// 启动3个worker
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// 发送任务
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
// 收集结果
for r := range results {
fmt.Println("Result:", r)
}
}
说明:
通道在这里作为任务队列;
workers 并发执行任务;
不需手动同步访问队列。
传统对比:
C/C++/Java 通常需:
一个共享任务队列;
一个互斥锁;
一个条件变量;
Go 的通道天然提供同步与阻塞语义。
- 点赞
- 收藏
- 关注作者
评论(0)