GO的并发模式

68 阅读4分钟

我们会学习三个在实际工程中使用的包

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可以了解有多少正在执行。