package main
import (
"fmt"
"net/http"
"sync"
"time"
)
func main() {
http.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
startTime := time.Now()
taskHandler()
fmt.Println("共计耗时:", time.Since(startTime).String())
})
if err := http.ListenAndServe(":18083", nil); err != nil {
panic(any(err))
}
}
func taskHandler() {
//模拟总任务数量
taskNums := 7
//定义最大可运行中的任务数量, 也可以理解为同时运行的最大goroutine数量
maxRuntimeTask := 3
wg := &sync.WaitGroup{}
wg.Add(taskNums)
//定义一个总调度完成后的通知队列, 在wg.wait()后来发送消息
noticeChan := make(chan struct{}, 1)
//定义一个协程池, 容量为maxRuntimeTask
poolChan := make(chan struct{}, maxRuntimeTask)
//定义一个任务完成通知队列, 容量为maxRuntimeTask
taskNoticeChan := make(chan struct{}, maxRuntimeTask)
defer func() {
//所有的goroutine都已经完成, 作用时监听者收到消息后会做出一个停止继续监听的动作
noticeChan <- struct{}{}
//关闭相关的通道队列
close(taskNoticeChan)
close(poolChan)
close(noticeChan)
}()
//这里单独起一个协程去做监听工作
go func() {
for {
select {
//每隔2s的一次检测
case <-time.After(2 * time.Second):
if len(taskNoticeChan) == maxRuntimeTask {
fmt.Printf("2s周期检测监听器>>> 当前poolChan长度:%d 当前taskNoticeChan长度:%d, 满足释放条件, 即将释放坑位... \n", len(poolChan), len(taskNoticeChan))
for i := 0; i < maxRuntimeTask; i++ {
fmt.Printf("释放坑位[%d]成功... \n", i)
//释放协程池队列
<-poolChan
//释放任务完成通知队列
<-taskNoticeChan
}
} else {
fmt.Printf("2s周期检测监听器>>> 当前poolChan长度:%d 当前taskNoticeChan长度:%d, 不满足释放条件, 即将进入下一轮释放周期... \n", len(poolChan), len(taskNoticeChan))
}
//接收全部任务完成的通知, 目的是为了防止goroutine泄漏
case <-noticeChan:
goto END
}
}
END:
fmt.Println("全部任务已执行完毕!")
}()
//模拟开启taskNums个任务
for i := 1; i <= taskNums; i++ {
//向协程池中发送一次数据, 如果当前协程池被塞满, 那么就会阻塞在这里, 直到有坑位才会继续开启新的goroutine
//这里的设计可以实现保证了最大运行中的goroutine数量
poolChan <- struct{}{}
fmt.Printf("任务[%d]抢占坑位成功... \n", i)
go func(i int) {
defer wg.Done()
//模拟一个5s的耗时任务
time.Sleep(5 * time.Second)
fmt.Printf("任务[%d]执行完毕, %s \n", i, time.Now().Format("2006-01-02 15:04:05"))
//任务执行完毕后需要向任务完成队列发送一次数据
taskNoticeChan <- struct{}{}
}(i)
}
wg.Wait()
}