我们会学习三个在实际工程中使用的包
runner
runner包用于展示如何使用通道来监视程序的执行时间,如果程序执行时间太长了,也可以使用runner来终止程序。
Runner的定义:
//Runner在给定的时间内执行任务,当接收到操作系统中断的时候结束这些任务
type Runner struct{
interrupt chan os.Signal //接受从OS的信号 接受os.signal接口类型的值
complete chan error // 报告任务是否完成,正常完成就是返回nil,否则返回error
timeout <-chan time.Time // 报告任务是否超时,接受到数据就准备清理状态并停止工作
tasks []func(int) // 要执行的函数,会有一个goroutine去执行这些函数
}
func New(d time.Duration) *Runner{
return &Runner{
interrupt : make(chan os.Signal,1),
//配合signal.Notify保证发送通道不会被堵塞,当没有准备接受的时候
//这个值会被丢弃。所以只得到准备接受的时候的最新值(新值会覆盖旧值)
complete : make(chan error),
timeout: time.After(d),
//After 返回一个time.Time类型的通道,当指定的时间到了后向通道发送值。
// 可以这样规范程序的运行时间
}
}
func (r *Runner)Add(tasks ...func(int)){
r.tasks = append(r.tasks, tasks...)
} // 使用...append 可变参数
func (r *Runner) run() error{
for id,task := range r.tasks{
if r.gotInterrupt(){
return ErrInterrupt
}
task(id)
}
return nil
} // error返回怎么写,迭代切片
// select+default的经典用法。 go中的case更加灵活
func (r *Runner) gotInterrupt() bool{
select{
case <-r.interrupt: // 如果切片返回
signal.Stop(r.interrupt) // 停止接受所有切片
return true
default:
return false
}
}
func (r *Runner) Start() error{
signal.Notify(r.interrupt,os.Interrupt)
go func(){
r.complete <- r.run()
}()
select{
case e := <-r.complete:
return e
case <- r.timeout:
return ErrTimeout
}
}
select 加通道接收,按道理select是要阻塞的,直到接受到数据,但是加了default就不会阻塞了。如果没有信号,执行default
当select中有多个通道已经准备就绪,select随机准备一个运行
runner 包的主要作用是管理长时间运行的任务,并在接收到中断信号(如操作系统的中断信号)或达到预设的超时时间时,优雅地关闭这些任务。以下是它的使用例子:
func main() {
fmt.Println("Starting application...")
// 设置超时时间为5秒
r := runner.New(5 * time.Second)
// 添加任务
r.Add(createTask(), createTask(), createTask())
// 运行任务并处理结果
if err := r.Start(); err != nil {
switch err {
case runner.ErrTimeout:
fmt.Println("Terminating due to timeout.")
os.Exit(1)
case runner.ErrInterrupt:
fmt.Println("Terminating due to interrupt.")
os.Exit(2)
}
}
fmt.Println("Tasks completed successfully.")
}
POOL包
这个包用于展示如何使用有缓冲的通道实现资源池,来管理任意数量goroutine之间共享及独立使用的资源。如果goroutine需要从池里得到这些资源的一个,它可以从池里申请,使用完后归还到资源池里
type Pool struct{
m sync.Mutex //保证池内的值安全
resources chan io.Closer//存储池内数据,只管理io.Closer接口类型的数据
factory func()(io.Closer,error)//接受参数为空,返回值为(io.Closer,error)的函数赋值
// 当池需要一个新资源时,由这个函数创建。需要包的使用者自己实现
closed bool //标志pool是否被关闭
}
func (p *Pool) Acquire() (io.Closer,error){
select{
//如果由空闲资源:
case r,ok := <-p.resources:
log.Println("Acquire:","Shared Resources")
if(!ok){
return nil,ErrPollClosed
}
return r,nil
default: // 没有空闲资源,提供一个新的资源
log.Println("Acquire:","New Resource")
return p.factory()
}
}
func (p *Pool) Close(){
p.m.Lock();
defer p.m.Unlock();
if p.closed{
return
}
p.closed = true
close(p.resources)//关闭通道
for r:= range p.resources{
r.Close() //销毁资源
}
}
// 把用完的数据放回池中
func (p *Pool) Release(r io.Closer){
p.m.Lock()
defer p.m.Unlock()
if p.closed{
r.Close()
return
}
select{
case p.resources <- r:
log.Println("Release :", "In Queue")
default:
r.Close()
log.Println("Release :", "Closing")
}
}
close 和 release必须互斥,上锁。 是因为保证他们两个操作不能同时发生,如果release执行的时候,由其他goroutine close了这个pool,那么我们就是在往已经关闭了的通道里发送数据,这样会引起崩溃。对close标志的读写必须同步 其次就是为了避免两个release和两个Close同时进行。
Pool的使用: 先定义factory函数和通道大小,把数据类型实现Close函数,实现io.Closer的接口。 其次传入函数和通道大小定义pool,每个goroutine只需要Acquire这个Pool和Release就行了。
Work
Work包的目的时展示如何使用无缓冲的通道来创建一个goroutine的池,这些goroutine执行并控制一组动作,使其并发。
type Worker interface{
Task()
}
type Pool struct{
work chan Worker
wg sync.WaitGroup
}
func new(maxGrouptines int) *Pool{
p := Pool{
work : make(chan Worker),
}
p.wg.Add(maxGrouptines)
for i := 0; i < maxGrouptines; i++{
go func(){
for w := range p.work{ // 这是个死循环。for range会一直阻塞,直到接收到通道里的接口值。当通道关闭,循环结束。
w.Task()
}// 同时也告诉了我们for迭代work的方式。
p.wg.Done()
}()
}
return &p
}
func (p *Pool) Run(w Worker){
p.work<-w
} // 向进程池中派送任务
// 由于是无缓冲通道,因此函数会等到池中的某一个goroutine接收到才返回,正是我们想要的,保证RUN返回时,程序已经运行。
通常使用:
go func(){
P.Run(&np)
wg.Done()
}() // 因为Run会堵塞,其次Run返回后代表正在执行,所有加一个wg可以了解有多少正在执行。