【Linux取经路】进度条小程序

【Linux取经路】进度条小程序

一、背景与问题

在Linux系统中,开发终端交互程序时常常需要显示进度条。传统做法是通过cat /proc/loadavg或ps命令获取进程信息,但这种方式存在精度低、实时性差等问题。本文将深入探讨如何在Linux终端中实现高性能的进度条小程序,重点分析底层原理和工程实践。

二、基本原理

1. 终端控制机制

Linux终端通过ANSI转义码实现光标控制和文本清除。关键码序列包括:

  • \x1b[2K:清除当前行
  • \x1b[1000D:光标左移1000格
  • \x1b[1;20;10H:光标移动到行1列20的位置

2. 进程间通信机制

使用pipe和fork实现父子进程通信,父进程负责任务处理,子进程负责进度显示。通过waitpid和WIFEXITED检测子进程状态。

3. 线程同步机制

使用pthread_mutex_t和pthread_cond_t实现线程同步,确保进度计算和显示操作的原子性。

三、环境准备

# 安装开发工具
sudo apt install build-essential

# 创建项目目录
mkdir progressbar && cd progressbar

四、核心实现

1. ANSI转义码实现(C语言)

#include <stdio.h>
#include <unistd.h>

void update_progress(int percent) {
    printf("\x1b[2K\x1b[1000D%d%%\r", percent);
    fflush(stdout);
}

关键代码解释:

  • \x1b[2K 清除当前行内容
  • \x1b[1000D 将光标左移1000格(覆盖整个终端宽度)
  • \r 将光标回退到行首
  • fflush(stdout) 确保立即刷新缓冲区

2. 管道通信实现(C语言)

#include <sys/wait.h>
#include <sys/types.h>

int main() {
    int pipefd[2];
    pid_t pid;

    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }

    pid = fork();
    if (pid == 0) {
        close(pipefd[0]);
        char buffer[100];
        while (read(pipefd[1], buffer, sizeof(buffer)) > 0) {
            printf("Child: %s\n", buffer);
            sleep(1);
        }
        exit(0);
    } else {
        close(pipefd[1]);
        for (int i = 0; i < 100; i++) {
            char msg[3] = {i + '0', '\n', '\0'};
            write(pipefd[0], msg, 2);
            sleep(1);
        }
        waitpid(pid, NULL, 0);
    }
    return 0;
}

关键代码解释:

  • pipe()创建管道用于父子进程通信
  • fork()创建子进程
  • write()向管道写入进度数据
  • read()读取进度数据并更新显示

3. 线程同步实现(C语言)

#include <pthread.h>

typedef struct {
    int percent;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} ProgressContext;

void* worker(void* arg) {
    ProgressContext* ctx = (ProgressContext*)arg;
    pthread_mutex_lock(&ctx->mutex);
    while (ctx->percent < 100) {
        pthread_cond_wait(&ctx->cond, &ctx->mutex);
        printf("\x1b[2K\x1b[1000D%d%%\r", ctx->percent);
        fflush(stdout);
        ctx->percent += 10;
    }
    pthread_mutex_unlock(&ctx->mutex);
    return NULL;
}

int main() {
    ProgressContext ctx = {0, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};
    pthread_t thread;
    pthread_create(&thread, NULL, worker, &ctx);

    for (int i = 0; i < 10; i++) {
        ctx.percent += 10;
        pthread_mutex_lock(&ctx.mutex);
        pthread_cond_signal(&ctx.cond);
        pthread_mutex_unlock(&ctx.mutex);
        sleep(1);
    }
    pthread_join(thread, NULL);
    return 0;
}

关键代码解释:

  • pthread_mutex_lock()和pthread_mutex_unlock()实现互斥锁
  • pthread_cond_wait()和pthread_cond_signal()实现条件变量通知
  • 确保进度更新和显示操作的原子性

五、完整案例

文件复制进度条工具(Python)

import os
import sys
import time
import threading

def copy_file(src, dst):
    with open(src, 'rb') as fsrc:
        with open(dst, 'wb') as fdst:
            total = os.path.getsize(src)
            read_bytes = 0
            while read_bytes < total:
                buf = fsrc.read(1024 * 1024)
                if not buf:
                    break
                fdst.write(buf)
                read_bytes += len(buf)
                percent = (read_bytes / total) * 100
                progress_bar(percent)

def progress_bar(percent):
    bar_length = 20
    filled_length = int(bar_length * percent / 100)
    bar = '█' * filled_length + '-' * (bar_length - filled_length)
    print(f"\r{bar} {percent:.2f}%", end='', flush=True)
    time.sleep(0.1)

def main():
    src = 'large_file.bin'
    dst = 'large_file_copy.bin'
    print("Starting file copy...")
    threading.Thread(target=copy_file, args=(src, dst)).start()
    while os.path.exists(dst):
        time.sleep(0.5)
    print("\nFile copy completed.")

if __name__ == '__main__':
    main()

关键点分析:

  • 使用多线程分离复制和进度显示逻辑
  • flush=True确保立即刷新缓冲区
  • time.sleep(0.1)控制刷新频率
  • 使用'█'字符创建进度条

六、源码解析

以C语言管道通信示例为例,分析关键部分:

  1. 管道创建:

    if (pipe(pipefd) == -1) {
     perror("pipe");
     return 1;
    }
  2. 创建两个文件描述符:pipefd[0]用于读取,pipefd[1]用于写入
  3. 系统调用pipe()会创建匿名管道
  4. 子进程处理:

    while (read(pipefd[1], buffer, sizeof(buffer)) > 0) {
     printf("Child: %s\n", buffer);
     sleep(1);
    }
  5. 读取父进程发送的进度数据
  6. sleep(1)模拟任务执行时间
  7. 父进程发送:

    for (int i = 0; i < 100; i++) {
     char msg[3] = {i + '0', '\n', '\0'};
     write(pipefd[0], msg, 2);
     sleep(1);
    }
  8. 每秒发送一次进度信息
  9. 使用write()将数据写入管道

七、进阶使用

1. 支持多任务进度显示

#include <sys/wait.h>
#include <sys/types.h>

int main() {
    int pipes[2];
    pid_t pid1, pid2;

    if (pipe(pipes) == -1) {
        perror("pipe");
        return 1;
    }

    pid1 = fork();
    if (pid1 == 0) {
        close(pipes[0]);
        char buffer[100];
        while (read(pipes[1], buffer, sizeof(buffer)) > 0) {
            printf("Task1: %s\n", buffer);
            sleep(1);
        }
        exit(0);
    }

    if (pipe(pipes) == -1) {
        perror("pipe");
        return 1;
    }

    pid2 = fork();
    if (pid2 == 0) {
        close(pipes[0]);
        char buffer[100];
        while (read(pipes[1], buffer, sizeof(buffer)) > 0) {
            printf("Task2: %s\n", buffer);
            sleep(1);
        }
        exit(0);
    }

    close(pipes[1]);
    for (int i = 0; i < 5; i++) {
        char msg[3] = {i + '0', '\n', '\0'};
        write(pipes[0], msg, 2);
        sleep(1);
    }
    waitpid(pid1, NULL, 0);
    waitpid(pid2, NULL, 0);
    return 0;
}

2. 支持百分比和剩余时间显示

#include <time.h>

void update_progress(int percent, double time_left) {
    printf("\x1b[2K\x1b[1000DProgress: %d%% | Remaining: %.2f sec\r", percent, time_left);
    fflush(stdout);
}

八、性能与工程实践

1. 性能优化

  • 刷新频率控制:使用time.sleep(0.1)控制刷新频率,避免过度消耗CPU
  • 双缓冲技术:先构建完整的进度条字符串,再一次性刷新
  • 缓冲区大小优化:避免频繁的小数据写入,使用固定大小缓冲区

2. 异常处理

  • 信号处理:添加signal(SIGINT, handler)处理中断信号
  • 资源释放:在atexit()注册清理函数
  • 错误日志:使用syslog()记录关键错误信息

3. 安全风险

  • 权限控制:确保脚本只对需要的用户可执行
  • 输入过滤:对来自外部的输入数据进行校验
  • 资源限制:使用ulimit限制进程资源使用

九、常见问题与踩坑

1. 进度条不显示

错误代码:

printf("%d%%\n", percent);

问题分析:

  • 没有使用ANSI转义码导致光标位置不正确
  • 缺少fflush(stdout)导致缓冲区未刷新

解决方案:

printf("\x1b[2K\x1b[1000D%d%%\r", percent);
fflush(stdout);

2. 多线程竞争

错误代码:

printf("Progress: %d%%\n", percent);

问题分析:

  • 多线程同时写入时可能造成输出混乱
  • 缺乏互斥锁保护

解决方案:

pthread_mutex_lock(&mutex);
printf("Progress: %d%%\n", percent);
pthread_mutex_unlock(&mutex);

3. 管道通信失败

错误代码:

write(pipefd[0], msg, 2);

问题分析:

  • 错误使用了读取端文件描述符
  • 未检查写入是否成功

解决方案:

if (write(pipefd[1], msg, 2) <= 0) {
    perror("write");
}

十、最佳实践

  1. 使用ANSI转义码:确保跨终端兼容性
  2. 多线程分离逻辑:保持主线程简洁
  3. 控制刷新频率:避免过度消耗资源
  4. 添加异常处理:确保程序健壮性
  5. 使用日志系统:替代printf输出
  6. 进行压力测试:验证在高负载下的表现

十一、总结

Linux终端进度条小程序的实现涉及终端控制、进程通信和线程同步等多个底层技术。通过深入分析ANSI转义码机制、管道通信原理和线程同步策略,我们可以构建出高效可靠的进度显示系统。在实际项目中,这种技术适用于需要用户反馈的后台任务、大型文件传输和长时间运行的批处理任务。但需要注意避免在实时数据处理、高并发场景或对性能要求极高的场合使用。通过合理的设计和优化,可以实现既美观又高效的终端交互体验。

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日