协程池是固定有数量
goroutine
的工作池Pool,Pool中的goroutine
并发地执行任务。
一、定义任务
在实现协程池前需要明确任务是啥,这里模拟一个场景:将一个文件的文本转化为小写并输出到一个新的文件中。
1、定义任务接口
任务接口定义了单个任务需要执行的任务内容,如这里的Do()
方法
type Tasker interface {
Do()
}
2、实现任务接口
- 具体的任务是将一个大文件的文本
input.txt
转换为小写并输出到output.txt
文件中。 - 定义具体的任务结构体
type Task struct {
text string // 单次处理的文本
results chan string // 结果channel,各任务之间共享信道
}
- 定义具体执行的任务
func (t *Task) Do() {
result := strings.ToUpper(t.text)
t.results <- result
}
二、定义协程池
协程池需要定义池的容量,任务消息队列,通信队列。
1、定义协程池
type Pool struct {
cap int // 协程池的容量
jobs chan Tasker // 任务通道
done chan interface{} // 各个G结束信号的消息通道
}
2、定义协程池相关操作
(1)构造函数
func NewTask(cap int) *Pool {
return &Pool{
cap: cap,
jobs: make(chan Tasker),
done: make(chan interface{}, cap),
}
}
(2)池运行函数
func (p *Pool) Run() {
for i := 0; i < p.cap; i++ { // 创建cap个G
go p.run()
}
}
(3)池中的单个G运行函数
func (p *Pool) run() {
for task := range p.jobs {
task.Do()
}
p.done <- nil // 执行结束之后写入完成信号
}
(4)关闭池函数
func (p *Pool) Close() {
close(p.jobs)
for i := 0; i < p.cap; i++ {
<-p.done // 等待各个G结束
}
}
三、主函数
func main() {
const outPath = "output.txt"
const inPath = "testCase01"
f, e := os.Open(inPath)
if e != nil {
panic(e)
}
defer f.Close()
lines := make(chan string)
outs := make(chan string)
pool := NewPool(3)
go func() { // 读者G,将文本按行输入到channel
defer close(lines)
rder := bufio.NewReader(f)
for {
l, _, e := rder.ReadLine()
if e != nil {
break
}
lines <- string(l)
}
}()
go func() { // 任务注册G,将lines的内容注册任务。
defer pool.Close()
defer close(outs)
for line := range lines {
pool.Register(&Task{
text: line,
results: outs,
})
}
}()
pool.Run() // 开启协程池。
fOut, e := OpenFile(outPath)
if e != nil {
panic(e)
}
w := bufio.NewWriter(fOut)
for o := range outs {
w.WriteString(o)
w.WriteByte('\n')
w.Flush()
}
}
// OpenFile 打开文件,不存在则创建
func OpenFile(out string) (*os.File, error) {
if _, e := os.Stat(out); os.IsNotExist(e) {
return os.Create(out)
} else {
return os.OpenFile(out, os.O_WRONLY, 0666)
}
}
- 代码实现了多个G并发地执行任务,如何对该代码进一步优化,待下回分解…