12-10 案例:并发任务执行与函数回调

1. 问题来源

        使用多线程进行文件下载,下载功能的实现由线程睡眠模拟,下载进度在控制台进行查看。

        此问题可使用一个主线程与多个子线程解决。其中主线程功能为:

                1) 查看各子线程的下载进度

                2) 查看剩余的待下载任务数量

        子线程功能为:

                1) 执行下载任务

                2) 下载任务完成后,发出通知,执行回调函数

#include <stdio.h>
#include <tinycthread.h>
#include <sys/timeb.h>

/*
 * 下载文件的案例
 *  1.主线程功能:
 *   1) 查看各子线程的下载进度
 *   2) 查看剩余的待下载任务数量
 *
 *  2.子线程功能:
 *   1) 执行下载任务
 *   2) 下载任务完成后,发出通知,执行回调函数
 */

// 定义各线程之间的上下文
typedef struct Context {
  mtx_t mutex;
  int download_left;  // 主线程中待下载任务数量,各子线程共享,故加锁才能修改
} Context;

// 子线程执行下载任务时接收的请求
typedef struct DownloadRequest {
  Context *context;  // 线程上下文
  char const *url;  // 下载地址
  char const *filename;  // 下载文件名
  int progress;  // 下载进度
  int interval;  // 下载时的模拟参数(下载速度)
  void (*callback)(struct DownloadRequest *);  // 函数回调,本质是定义一个函数指针,在下载完成后执行该函数,发出下载完成的通知
} DownloadRequest;

// 线程睡眠
void SleepMs(long milliseconds) {

  // 获取系统当前时间
  struct timeb time_buffer;
  ftime(&time_buffer);

  // 当前时间之后睡眠多长时间
  long sec = milliseconds / 1000;  // 获取睡眠秒数
  long nsec = (milliseconds % 1000) * 1000000L;
  thrd_sleep(&(struct timespec) {.tv_sec = time_buffer.time + sec,
      .tv_nsec = time_buffer.millitm * 1000000L + nsec}, NULL);
}

// 子线程下载文件
int DownloadFile(DownloadRequest *request) {

  // 打印下载提示
  printf("\rDownloading file from: %s into %s ...\n", request->url, request->filename);

  // 模拟下载过程
  for (int i = 1; i < 101; ++i) {
    SleepMs(request->interval);// 使用线程睡眠模拟下载
    request->progress = i;
  }

  // 下载完成,执行回调,发出通知
  request->callback(request);  // 注意,不要忘记传参数,request->callback 就相当于函数名

  return 0;
}

/*
 * 子线程下载完成后执行的回调函数
 *   1. 修改剩余下载任务数
 *   2. 发出完成任务通知
 */
void DownloadCallBack(DownloadRequest *request) {

  // 由于修改主线程变量,需要加锁
  mtx_lock(&request->context->mutex);

  // 1. 修改剩余下载任务数
  request->context->download_left--;

  // 2. 发出完成任务通知
  printf("\rDownload file from %s into %s successfully, left: %d\n",
         request->url, request->filename, request->context->download_left);

  mtx_unlock(&request->context->mutex);
}

/*
 * 主线程管理整体的下载情况
 *   1. 初始化下载请求,线程上下文
 *   2. 创建子线程进行下载
 *   3. 主线程实时查看下载进度
 */
void MainDownload(char *urls[], char *filenames[]){

#define DOWNLOAD_TASKS 5  // 定义下载任务个数

  // 1. 初始化线程上下文,及下载请求
  Context context = {.download_left = DOWNLOAD_TASKS};  // 初始化线程上下文
  mtx_init(&context.mutex, mtx_plain);   // 初始化线程上下文中的锁

  DownloadRequest request[DOWNLOAD_TASKS];    // 创建五个下载请求
  for (int i = 0; i < DOWNLOAD_TASKS; ++i) {  // 初始化下载请求
    request[i] = (DownloadRequest){
      .context = &context,
      .url = urls[i],
      .filename = filenames[i],
      .progress = 0,
      .interval = i * 100 + 50,  // 不同线程下载速度
      .callback = DownloadCallBack
    };  // 此处利用匿名结构体进行初始化操作,也可使用 request[i].context, request[i].url... 这样一个个进行初始化
  }

  // 2. 创建子线程进行下载
  for (int i = 0; i < DOWNLOAD_TASKS; ++i) {
    thrd_t t;
    thrd_create(&t, DownloadFile, &request[i]);
    thrd_detach(t);  // 由于主线程需要实时查看下载进度,所以不等待子线程运行完毕,使用 detach,让子线程自己运行
  }

  // 3. 主线程实时查看下载进度 (周期性刷新控制台,查看下载进度)
  while (1){

    // 加锁获取当前剩余下载任务数
    mtx_lock(&context.mutex);
    int download_left = context.download_left;  // 使用局部变量获取全局变量的值,这样就不再访问共享资源
    mtx_unlock(&context.mutex);

    // 判断是否下载完成
    if(download_left == 0){
      break;
    }

    // 没下载完成的情况下,显示下载进度
    printf("\r");  // 光标回到首部
    for (int i = 0; i < DOWNLOAD_TASKS; ++i) {
      printf("%s -- %3d%% \t", request[i].filename, request[i].progress); // 注意,打印出 % 不是 \%,而是 %%
    }

    fflush(stdout);  // 刷新缓冲区,不然不能实时查看
    SleepMs(100);  // 每 100 ms 查看一次
  }

  mtx_destroy(&context.mutex);  // 销毁线程上下文中的锁
}

int main() {

  char *urls[] = {
      "https://www.baidu.com/file1",
      "https://www.baidu.com/file2",
      "https://www.baidu.com/file3",
      "https://www.baidu.com/file4",
      "https://www.baidu.com/file5"
  };

  char *filenames[] = {
      "file1",
      "file2",
      "file3",
      "file4",
      "file5"
  };

  MainDownload(urls, filenames);

  return 0;
}

        使用 msvc 或 mingw 编译器查看时,程序执行结果较乱,原因在于控制台光标输出后自动换行,\r 是在下一行打印,执行结果为:

        可使用 windows power shell 执行程序,光标输出后不会自动换行,其执行结果为:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
并发执行和并行执行是计算机领域中常用的两个概念,它们都指的是多个任务同时执行的情况,但是具体的实现方式和效果略有不同。 并发执行指的是多个任务在同一时间段内交替执行的过程。在单处理器系统中,每个任务都只能在一个时间片内执行一部分,然后被挂起,等待下一个时间片再次执行。由于时间片极短,任务执行看起来是同时进行的,因此被称为并发执行并发执行可以提高系统的吞吐量和响应速度,但是由于任务之间需要频繁地切换,会增加系统的开销和复杂度。 并行执行指的是多个任务在多个处理器上同时执行的过程。在多处理器系统中,每个任务可以分配到不同的处理器上执行,从而实现真正的并行执行。由于任务在不同的处理器上执行,彼此之间互不干扰,因此可以大大提高系统的计算能力和吞吐量。但是并行执行需要满足任务之间可以并行执行的条件,如任务之间的依赖关系、数据共享等,否则可能会出现竞争和冲突,导致执行结果不正确。 综上所述,并发执行和并行执行都是多任务执行的方式,但是并发执行是在单处理器上通过时间片轮转等技术实现的,而并行执行是在多处理器上通过任务分配和并行执行实现的。两者都有其适用的场景和优缺点,需要根据具体的应用需求和硬件条件进行选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值