首页 > 编程语言 >C++编程:通过多线程与协程优化阻塞型任务的调度性能

C++编程:通过多线程与协程优化阻塞型任务的调度性能

时间:2024-11-19 16:46:46浏览次数:3  
标签:std 协程 thread int C++ params 多线程 row

文章目录

0. 引言

我们知道:

  • 多线程:适用于处理计算密集型任务或IO操作较少的场景,但会因为线程切换和创建销毁的开销而影响性能。
  • 协程:适用于处理需要大量并发且具有等待或阻塞的任务。在阻塞型任务中,协程通过上下文切换以提高资源利用率,减少开销。

本文将探讨如何通过 多线程协程 来优化 阻塞型任务(例如矩阵乘法)调度性能,使用的协程库来自:state-threads
更多请看:state-threads-for-internet-applications

基本使用方法如下:

#include <stdio.h>
#include "st.h"

void* do_calc(void* arg){
    int sleep_ms = (int)(long int)(char*)arg * 10;
    for(;;){
        printf("in sthread #%dms\n", sleep_ms);
        st_usleep(sleep_ms * 1000);
    }
    return NULL;
}

int main(int argc, char** argv){
    if(argc <= 1){
        printf("Test the concurrence of state-threads!\n");
        printf("Usage: %s <sthread_count>\n");
        printf("eg. %s 10000\n", argv[0], argv[0]);
        return -1;
    }
    if(st_init() < 0){
        printf("st_init error!");
        return -1;
    }
    
    int i;
    int count = atoi(argv[1]);
    for(i = 1; i <= count; i++){
        if(st_thread_create(do_calc, (void*)i, 0, 0) == NULL){
            printf("st_thread_create error!");
            return -1;
        }
    }
    st_thread_exit(NULL);
    return 0;
}

1. 多线程 VS 多线程+协程

来看一个简单的 矩阵乘法 的实现。我们将矩阵分割成多个部分,每个线程负责计算矩阵的一部分。为模拟阻塞行为,我们在计算过程中加入了 std::this_thread::sleep_for(std::chrono::microseconds(1)) 延迟1微秒,在协程中使用st_usleep(1);让出CPU 1微秒。

1.1 示例 1:使用传统的多线程进行矩阵乘法

#include <iostream>
#include <vector>
#include <thread>
#include <random>
#include <chrono>

// 生成随机矩阵
std::vector<std::vector<int>> generate_matrix(int rows, int cols) {
  std::vector<std::vector<int>> matrix(rows, std::vector<int>(cols));
  std::random_device rd;
  std::mt19937 gen(rd());
  std::uniform_int_distribution<> dis(1, 100);

  for (int i = 0; i < rows; ++i) {
    for (int j = 0; j < cols; ++j) {
      matrix[i][j] = dis(gen);
    }
  }
  return matrix;
}

// 线程函数:矩阵乘法的一部分
void thread_matrix_multiply(const std::vector<std::vector<int>>& A, const std::vector<std::vector<int>>& B,
                            std::vector<std::vector<int>>& C, int start_row, int end_row) {
  int m = A.size();
  int n = A[0].size();
  int p = B[0].size();

  for (int i = start_row; i < end_row; ++i) {
    for (int j = 0; j < p; ++j) {
      C[i][j] = 0;
      for (int k = 0; k < n; ++k) {
        C[i][j] += A[i][k] * B[k][j];
      }
      std::this_thread::sleep_for(std::chrono::microseconds(1));  // 模拟阻塞
    }
  }
}

void multi_thread_matrix_multiply(const std::vector<std::vector<int>>& A, const std::vector<std::vector<int>>& B,
                                  std::vector<std::vector<int>>& C, int num_threads) {
  int m = A.size();
  int n = A[0].size();
  int p = B[0].size();

  std::vector<std::thread> threads;
  int rows_per_thread = m / num_threads;

  for (int i = 0; i < num_threads; ++i) {
    int start_row = i * rows_per_thread;
    int end_row = (i == num_threads - 1) ? m : start_row + rows_per_thread;

    threads.emplace_back(thread_matrix_multiply, std::ref(A), std::ref(B), std::ref(C), start_row, end_row);
  }

  for (auto& t : threads) {
    t.join();
  }
}

int main() {
  int N = 1000;
  int num_threads = 4;

  auto A = generate_matrix(N, N);
  auto B = generate_matrix(N, N);
  std::vector<std::vector<int>> C(N, std::vector<int>(N));

  // 记录开始时间
  auto start = std::chrono::high_resolution_clock::now();

  multi_thread_matrix_multiply(A, B, C, num_threads);

  // 记录结束时间并计算耗时
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> time_spent = end - start;
  std::cout << "Time spent: " << time_spent.count() << " seconds\n";

  return 0;
}

执行结果:

$ g++ -o mutil_thread mutil_thread.cpp -O2 -lpthread
$ ./mutil_thread
Start multi-thread matrix multiplication with 4 threads...
Start 4 threads...
All threads finished.
Time spent: 14.91s

1.2. 示例 2:使用协程优化阻塞型任务

接下来,我们使用 协程 来优化阻塞型任务的调度。在协程中,我们不再为每个任务创建独立的线程,而是在同一个线程中切换执行上下文。

#include <pthread.h>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <random>
#include <vector>

#include "st.h"

#define THREAD_COUNT 4
#define STACK_SIZE (4* 1024)  // 4k stack size

// 定义协程参数结构体
struct CoroutineParams {
  const std::vector<std::vector<int>>* A;
  const std::vector<std::vector<int>>* B;
  std::vector<std::vector<int>>* C;
  int start_row;
  int end_row;
  int* active_coroutines;  // 指向活跃协程计数器的指针
};

// 协程函数:矩阵乘法的一部分
void* coroutine_matrix_multiply(void* arg) {
  CoroutineParams* params = static_cast<CoroutineParams*>(arg);

  int m = params->A->size();
  int n = params->A->at(0).size();
  int p = params->B->at(0).size();

  for (int i = params->start_row; i < params->end_row; ++i) {
    for (int j = 0; j < p; ++j) {
      (*params->C)[i][j] = 0;
      for (int k = 0; k < n; ++k) {
        (*params->C)[i][j] += (*params->A)[i][k] * (*params->B)[k][j];
      }
      st_usleep(1);  // 模拟阻塞
    }
  }

  // 协程完成,减少活跃协程计数器
  __sync_fetch_and_add(params->active_coroutines, -1);

  delete params;
  return NULL;
}

// 每个线程执行的函数
void* thread_matrix_multiply(void* arg) {
  ThreadParams* params = static_cast<ThreadParams*>(arg);

  // 在每个线程中初始化State Threads库
  if (st_init() < 0) {
    fprintf(stderr, "st_init error in thread %ld!\n", pthread_self());
    pthread_exit(NULL);
  }

  int rows_per_coroutine = (params->end_row - params->start_row) / params->coroutine_count;

  // 初始化活跃协程计数器
  *params->active_coroutines = params->coroutine_count;

  for (int i = 0; i < params->coroutine_count; ++i) {
    int start_row = params->start_row + i * rows_per_coroutine;
    int end_row = (i == params->coroutine_count - 1) ? params->end_row : start_row + rows_per_coroutine;

    CoroutineParams* coroutine_params = new CoroutineParams{params->A, params->B, params->C, start_row, end_row};
    coroutine_params->active_coroutines = params->active_coroutines;

    if (st_thread_create(coroutine_matrix_multiply, coroutine_params, STACK_SIZE, 0) == NULL) {
      fprintf(stderr, "st_thread_create error! i=%d in thread %ld\n", i, pthread_self());
      pthread_exit(NULL);
    }
  }

  // 等待所有协程完成
  while (*params->active_coroutines > 0) {
    st_thread_yield();
  }

  delete params;
  return NULL;
}

int main() {
  int N = 1000;
  int num_threads = THREAD_COUNT;

  auto A = generate_matrix(N, N);
  auto B = generate_matrix(N, N);
  std::vector<std::vector<int>> C(N, std::vector<int>(N));

  // 记录开始时间
  clock_t start = clock();

  multi_thread_matrix_multiply(A, B, C, num_threads);

  // 记录结束时间并计算耗时
  clock_t end = clock();
  double time_spent = (double)(end - start) / CLOCKS_PER_SEC;
  std::cout << "Time spent:

 " << time_spent << " seconds\n";

  return 0;
}

执行结果:

$ g++ -o coroutine2 coroutine2.cpp -O2  -lst -lpthread -L.
$ ./coroutine2 
Start multi-thread matrix multiplication with 4 threads and 10 coroutines per thread...
Start 4 threads...
All threads finished.
Time spent: 3.32s

3. 分析与对比

实验是假设计算任务中存在阻塞操作,如std::this_thread::sleep_for(std::chrono::microseconds(1))。从结果可以看出,与传统的多线程方法相比,协程使用st_usleep(1)让出CPU吗,可以减少线程创建和上下文切换的开销,提升性能。

多线程(14.91秒)
每个线程都运行在独立的执行单元上,导致线程之间的上下文切换开销较大,尤其是在大量小任务(例如矩阵乘法中的每次加法)并发执行时。

协程(3.06秒):
协程通过在单线程中高效切换执行上下文,减少了线程调度开销。尽管任务本身仍然存在延迟,但由于多个协程共享同一个线程的CPU资源,整体执行效率大幅提升。

标签:std,协程,thread,int,C++,params,多线程,row
From: https://blog.csdn.net/stallion5632/article/details/143887766

相关文章

  • 实验4 C++
    任务2:GradeCalc.cpp1#pragmaonce2#include<iostream>3#include<vector>4#include<string>5#include<algorithm>6#include<numeric>7#include<iomanip>89usingstd::vector;10usingstd::......
  • C++ 学习笔记(1):STL、Vector 与 Set
    背景最近在尝试入坑蓝桥杯,于是先从C++开始学起,这里记个笔记。这里我的笔记是跟着这个教程来的。沙比学校天天整些屁事都没什么空折腾。前言笔者是JS/TS写的比较多,以前写过C但是有点忘了,所以文章里都是和JS进行对比着方便快速理解。同时其实我还有几个小问题,嘻嘻。没......
  • C++中的友元函数和友元类&友元的作用及注意事项
    1.C++中的友元函数和友元类友元函数:友元函数是指某些虽然不是类成员却能够访问类的所有成员的函数。类授予它的友元特别的访问权。通常,同一个开发者会出于技术和非技术的原因控制类的友元和成员函数,否则在更新类时,还需要征得其他部分的拥有者的同意。友元函数在定义上和调用......
  • 打卡信奥刷题(264)用C++信奥P2010[普及组/提高] [NOIP2016 普及组] 回文日期
    [NOIP2016普及组]回文日期题目背景NOIP2016普及组T2题目描述在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。牛牛习惯用888位数字表示一......
  • 详解 C++ 的内存序模型
    详解C++的内存序模型C++提供了内存序模型来控制多线程程序中不同线程对共享内存的访问顺序。最常用的是顺序一致性内存模型(memory_order_seq_cst),但它也提供了其他模型(如memory_order_relaxed)以优化性能。一、顺序一致性内存模型(memory_order_seq_cst)定义顺序一致性......
  • Python 基于C++ & python的键盘记录器发送指定邮箱
    Python基于C++&python的键盘记录器发送指定邮箱1.简介:采用c++与python语言相结合的方法,c++负责采集键盘操作记录到文本,python脚本实时将文本内容发送至指定邮箱。资源文件已打包,可设置开机自启动。2.kb.cpp键盘记录实现代码:#include<iostream>#include<stdio.h>......
  • 实现简易计算器 网格布局 QT环境 纯代码C++实现
    问题:通过代码完成一个10以内加减法计算器。不需要自适应,界面固定360*350。"="按钮90*140,其它按钮90*70。参考样式#defineDEFULT_BUTTON_STYLE"\QPushButton{\color:#000000;\border:1pxsolid#AAAAAA;\border-radius:0;\background-color:#FFFFFF;......
  • (分享源码)计算机毕业设计必看必学 上万套实战教程手把手教学JAVA、PHP,node.js,C++、pyth
     摘 要21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认知向理性认知提高,管理工作的重要性已逐渐被人们所认识,科学化的管理,使信息存储达到准确、快速、完善,并能提高工作管理效率,促进其发展。论文主要是对医疗门诊管理......
  • (分享源码)计算机毕业设计必看必学 上万套实战教程手把手教学JAVA、PHP,node.js,C++、pyth
     摘 要随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,校园跳蚤市场管理系统被用户普遍使用,为方便用户能够可以随时进行校园跳蚤市场管理系统的数据信息管理,特开发了基于spri......
  • 解锁C++第二大特性,代码也玩“父子”游戏——继承
    解锁C++第二大特性,代码也玩“父子”游戏——继承文章目录解锁C++第二大特性,代码也玩“父子”游戏——继承前言——封装封装的本质是什么?封装的总结一、继承的基本概念1.1继承的定义二、继承的三种方式2.1私有不可见2.2公有、保护的继承2.3基类和派生类对象赋值......