使用go实现一个可以显示进度条的线程池

使用go实现一个可以显示进度条的线程池

一、概述

​ 有些情况下,我们的程序可能同时有好几个goroutine在跑,并且你想要动态地显示每个goroutine的进度条(比如下载一些比较多大的文件的时候),如果你没有这个需求,只想利用多个线程去去执行一些任务,并不想显示进度条,想去设置参数,不让线程池显示
不得不承认,当前线程池还有一写细节地方可以改进,后续将会在gitee仓库(https://gitee.com/he_fu_ren/thread-pool-with-progress-bar)进行更新,如果有bug欢迎指出,将会即使修正

二、设计思路

线程池角色介绍:

任务池: 本质上就是存放所有任务的channel

goroutine的进度: 其实对应的是图中的goroutine_1goroutine_2goroutine_3

任务: 对每个线程所做的事的抽象,是goroutine的进度的一个属性

进度: 进度是每个任务的属性

总进度: 本质上是一个数组,保存着吗每个goroutine的进度,其实本质上是监控的每个任务进度

监听线程: 位于图中最右侧,可以监听每个goroutine的执行情况并做相应处理(如:打印进度条)

请添加图片描述

三、源代码

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
)

// FixedThreadPool 线程池
type FixedThreadPool struct {
	*TotalProgress         // 总体进程
	Tasks          []*Task // 所有任务
	GoroutineNum   int     // 线程数
	IsVisible      bool    // 是否显示进度条
}

// NewFixedThreadPool 创建线程池
func NewFixedThreadPool(tasks []*Task, goroutineNum int, isVisible bool) *FixedThreadPool {
	return &FixedThreadPool{
		Tasks:         tasks,
		TotalProgress: NewTotalProgress(len(tasks), goroutineNum),
		GoroutineNum:  goroutineNum,
		IsVisible:     isVisible,
	}
}

// TotalProgress 总进度
type TotalProgress struct {
	FinishNum   int                  // 完成数
	TaskNum     int                  // 总任务数
	AllProgress []*GoroutineProgress // 所有goroutine的进度
}

// NewTotalProgress 初始化总进度
func NewTotalProgress(taskNum, goroutineNum int) *TotalProgress {
	return &TotalProgress{
		FinishNum:   0,
		TaskNum:     taskNum,
		AllProgress: make([]*GoroutineProgress, goroutineNum),
	}
}

// GoroutineProgress 每个goroutine的进度
type GoroutineProgress struct {
	GoroutineId int  // goroutine的ID
	*Task            // 正在执行的任务
	isIdle      bool // 当前线程是否空闲
}

// Exec 执行任务
func (goProgress *GoroutineProgress) Exec() {
	goProgress.isIdle = false
	goProgress.Run(goProgress.TaskProgress) // 执行任务
	goProgress.isIdle = true
}

// Task 每个goroutine所执行的任务
type Task struct {
	TaskProgress *TaskProgress		 // 任务的进度取值范围[1-100]
	Run          func(*TaskProgress) // 执行
}

func (t Task) GetProgress() int {
	return t.TaskProgress.CurrentProgress
}

func NewTask(f func(*TaskProgress)) *Task {
	return &Task{
		TaskProgress: &TaskProgress{CurrentProgress: 0},
		Run:          f,
	}
}

// TaskProgress 任务的进度
type TaskProgress struct {
	CurrentProgress int // 任务进度单位%
}

// Exec 线程池开始执行任务
func (pool *FixedThreadPool) Exec() {
	// 将所有任务放入到channel中
	taskChannel := make(chan *Task, pool.TaskNum)
	for _, task := range pool.Tasks {
		taskChannel <- task
	}
	// 同步主线程与创建的goroutine
	wg := &sync.WaitGroup{}
	wg.Add(pool.TaskNum)
	// 通知线程关闭的channel,+1是为了关闭打印进度的线程
	done := make(chan struct{}, pool.GoroutineNum+1)

	// 开启goroutine线程
	for i := 0; i < pool.GoroutineNum; i++ {
		// 监听每个线程的进度
		progress := &GoroutineProgress{
			GoroutineId: i,
		}
		pool.TotalProgress.AllProgress[i] = progress

		// 开启线程
		go func(done chan struct{}, wg *sync.WaitGroup, progress *GoroutineProgress, totalProgress *TotalProgress) {
		Label:
			for {
				select {
				case progress.Task = <-taskChannel:
					progress.Exec()
					wg.Done()
					totalProgress.FinishNum++
				case <-done:
					// 结束
					break Label
				}
			}
		}(done, wg, progress, pool.TotalProgress)
	}

	if pool.IsVisible {
		// 单独开启一个监听正在执行的任务的进度的goroutine
		go func(done chan struct{}, totalProgress *TotalProgress) {
		Label:
			for {
				select {
				case <-done:
					break Label
				default:
					// 输出任务的进度
					fmt.Printf("当前进度: %d/%d\n", totalProgress.FinishNum, totalProgress.TaskNum)
					for _, progress := range totalProgress.AllProgress {
						if !progress.isIdle && progress.Task != nil {
							fmt.Printf("任务进度: [%s%s] %d%%\n", strings.Repeat("=", progress.GetProgress()), strings.Repeat(" ", 100-progress.GetProgress()), progress.GetProgress())
						}
					}
					// 间隔1s
					time.Sleep(time.Second)
					// 清屏
					fmt.Println("\033c")
				}
			}
		}(done, pool.TotalProgress)
	}

	// 等待所有任务执行结束
	wg.Wait()
	for i := 0; i <= pool.GoroutineNum; i++ {
		done <- struct{}{}
	}
	log.Println("所有任务执行完毕")
}

四、使用示例

显示进度条

代码

任务的本质其实就是一个func,参数是任务进度的指针,由程序猿自己根据业务决定进度的值
请添加图片描述

执行效果

1、
请添加图片描述

2、
请添加图片描述

3、
请添加图片描述

4、
请添加图片描述

不显示进度条

其实和和上面的唯一差别是在创建线程池的时候,其余的都一样

fixedThreadPool := NewFixedThreadPool(tasks, 2, false)	// 最后一个参数使用false,表示不打印进度
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
JavaFX中可以使用线程池实现进度条的更新。下面是一个简单的例子,它使用线程池来模拟一个长时间运行的任务,并且在任务执行期间更新进度条。 ```java import javafx.application.Application; import javafx.concurrent.Task; import javafx.concurrent.WorkerStateEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.ProgressBar; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ProgressBarDemo extends Application { private ProgressBar progressBar; @Override public void start(Stage primaryStage) throws Exception { StackPane root = new StackPane(); // 创建进度条 progressBar = new ProgressBar(); progressBar.setPrefWidth(200); // 添加进度条到布局中 root.getChildren().add(progressBar); // 创建一个用于模拟长时间运行任务的线程池 ExecutorService executor = Executors.newSingleThreadExecutor(); // 创建一个任务 Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { // 模拟一个长时间运行的任务 for (int i = 1; i <= 100; i++) { Thread.sleep(50); updateProgress(i, 100); } return null; } }; // 当任务完成时更新进度条 task.setOnSucceeded(new EventHandler<WorkerStateEvent>() { @Override public void handle(WorkerStateEvent event) { progressBar.setProgress(1.0); } }); // 在线程池中执行任务 executor.submit(task); primaryStage.setScene(new Scene(root, 200, 100)); primaryStage.show(); } public static void main(String[] args) { launch(args); } } ``` 在上面的例子中,我们创建了一个进度条一个用于模拟长时间运行任务的线程池。然后,我们创建了一个Task对象,并覆盖它的call()方法来执行我们的任务。在任务执行期间,我们使用updateProgress()方法来更新进度条的进度。最后,我们将任务提交到线程池中,并在任务完成时更新进度条
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生命中有太多不确定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值