2013-12-15 wcdj
并发编程的一种常见方式是有多个任务需要同时处理,并且每个任务都可以独立地完成。在go语言里每个任务都在一个独立的goroutine(协程)里处理,和其他协程之间没有任何通信。下面通过go语言的方式模拟一个经典的独立多任务并发模式,程序创建了3个带有缓冲区的双向通道,所有的工作都会分发给工作协程来处理,协程的总数量和当前机器的处理器数量相当,将不必要的阻塞尽可能降到最低。
PS: 并发编程是go语言的最大优势,而协程是实现这一优势的手段。只要需要在函数调用前加上关键字go,这个函数就会以一个单位协程的形式执行。go语言向线程派发这些函数的执行,当一个协程阻塞时,调度器会把其他协程转移到其他的线程中去执行,从而实现无等待的并行运行。协程的优势是减少资源的context切换提高执行效率,但缺点是不易于调试,需要调度器提供相关调试工具。
测试代码:
package main
import (
"fmt"
"runtime"
)
var workers = runtime.NumCPU()
type Result struct {
jobname string
resultcode int
resultinfo string
}
type Job struct {
jobname string
results chan<- Result
}
func main() {
// go语言里大多数并发程序的开始处都有这一行代码, 但这行代码最终将会是多余的,
// 因为go语言的运行时系统会变得足够聪明以自动适配它所运行的机器
runtime.GOMAXPROCS(runtime.NumCPU())
// 返回当前处理器的数量
//fmt.Println(runtime.GOMAXPROCS(0))
// 返回当前机器的逻辑处理器或者核心的数量
//fmt.Println(runtime.NumCPU())
// 模拟8个工作任务
jobnames := []string{"gerry", "wcdj", "golang", "C++", "Lua", "perl", "python", "C"}
doRequest(jobnames)
}
func doRequest(jobnames []string) {
// 定义需要的channels切片
jobs := make(chan Job, workers)
results := make(chan Result, len(jobnames))
done := make(chan struct{}, workers)
// ---------------------------------------------
/*
* 下面是go协程并发处理的一个经典框架
*/
// 将需要并发处理的任务添加到jobs的channel中
go addJobs(jobs, jobnames, results) // Executes in its own goroutine
// 根据cpu的数量启动对应个数的goroutines从jobs争夺任务进行处理
for i := 0; i < workers; i++ {
go doJobs(done, jobs) // Each executes in its own goroutine
}
// 新创建一个接受结果的routine, 等待所有worker routiines的完成结果, 并将结果通知主routine
go await