栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

C++ 并发

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 并发

文章目录
  • C++并发编程
    • 前言
    • 为什么需要并发
      • 定义
      • 并发的好处
    • 开始
      • 环境设置
      • 线程
        • 线程创建
        • join() && detach()
        • 线程的管理
        • 使用多线程
      • 互斥量和锁
        • 死锁
        • 简单的互斥管理
        • 条件变量
        • 原子操作
      • 异步
        • async
        • future

C++并发编程 前言

因为最近项目涉及到C++的并发相关内容,并且本科时期很少涉及到这些内容(传授过部分思想,但是实战却很少很少)所以这段时间好好的学习了一下C++的并发知识,作一下总结.

为什么需要并发 定义

上图是对于并发与并行之间区别的经典解释

  • 并发(Concurrent): 多个队列可以交替使用机器
  • 并行(Parallel): 存在多个机器供多个队列交替使用

再简单一点,如果一个系统支持多个任务同时存在,那这就是并法系统;如果还能支持同一时刻多个任务同时执行,那就是并行系统.

再从计算机角度来谈谈.每一台电脑都需要配置一套操作系统,它管理着电脑的资源并且向用户提供服务.其中,进程和线程是操作系统中的基本概念,进程是程序的基本执行实体,线程是操作系统能够进行运算调度的最小单位,包含于进程之中.一个进程最少包含一个线程(主线程),也可以包含多个线程.

比如我们打开QQ聊天,就是启动QQ这个进程.但是如果我们一边下载某个软件,一边和另外的好友聊天,二者之间互不影响这就是运用了多个线程.在早期,电脑只有一个处理器时,所有的进程线程都会分时占用这个处理器;现在电脑都是多核处理器,所以多个任务可以并行运行在不同的处理器中.

最后做个简单的总结,并发和并行都可以是调用了很多线程.如果这些线程能同时被多个处理器执行,那就是并行的;如果是轮流切换执行,那就是并发.

并发的好处

为什么我们现在都在提高并发呢?说到底还是得益于硬件发展,多核cpu已经普及.面对日益增长的大数据,传统的串行弊端逐渐显现;为了追求更高的效率我们就希望极致地发挥cpu性能.

相信大家小学的时候都遇见过这种类型的题目,早上起来刷牙三分钟,烧水五分钟,煎鸡蛋2分钟…
如果是串行,一次只能干一件事,那么前一件事没做完后面的任务全部得等待.但是如果是并发,那我们可以先起来把水烧了,然后去刷牙煎鸡蛋,这样就省了一半的时间,也就是效率提高了一倍.对于电脑来说也是如此,既然我们可以使用多线程,那为什么非要傻乎乎的盯着一个线程一个任务一个任务的执行呢?直接用多个线程一起干.

开始 环境设置

目前我使用的环境是ubuntu20,下面的例子在Win10/Win11应该也没问题.gcc编译器版本是11.1.0,c++标准用17/20都可以.

提示:在ubuntu下使用多线程需要添加-lpthread参数,不然会报错

gcc安装直接使用sudo apt-get install gcc-11

编辑器vscode/clion/vs… 这个就随意啦

线程 线程创建

在c++11之后创建线程是非常简单的,使用thread实例化线程对象就可以进行线程创建.

#include
#include

using namespace std;

void hello(){
    cout<<"Hello Thread is: "< 

当然,除了函数我们还可以传入lambda表达式

#include 
#include 
int main() {
    int a = 0;
    int flag = 0;

    std::thread t1([&]() {
        while (flag != 1);
        int b = a;
        std::cout << "b = " << b << std::endl;
    });

    std::thread t2([&]() {
        a = 5;
        flag = 1;
    });
    t1.join();
    t2.join();
    
    return 0;
}

如果函数需要传入参数,那么直接跟在函数名后面就行.但是参数是以拷贝的形式传递的,如果为了节省拷贝的时间也可以选择传入指针或者引用.但是如果指针或引用参数的生命周期小于线程的生命周期,这个时候就会出错.

join() && detach()
  • join() 调用时当前线程会一直阻塞直到目标线程执行完毕;
  • detach() 让目标线程成为守护线程,使目标线程独立执行.
线程的管理

在线程内部,我们可以对当前线程进行一些控制

方法说明
yield让出处理器,重新调度各线程
get_id得到当前线程的id
sleep_for使当前线程停止指定的一段时间
sleep_until使当前的线程停止直到指定的时间点

如果有些任务我们只需要执行一次,比如初始化加载一些资源的任务,那么还可以用一次调用的方式.即使有多个线程的情况下,相应的函数也仅仅调用一次.

#include
#include
#include
#include
#include

using namespace std;

void init(){
    cout<<"Initializing..."< 

可以看到即使有三个线程,但是init仅仅被执行了一次.

使用多线程

前面说了这么多,但是如何使用多线程来提高程序效率还是没有展现,所以下面的例子就来看看多线程的优势.

假设有这样的任务,需要计算1-10e7内所有数的和,使用串行写法很简单,直接遍历求和

void worker(int min,int max){
    for(int i=min;i 

如果想提高效率,那我们可以把任务分解交给多个线程去计算,但是最后计算汇总结果的时候涉及到临界区的竞争(多个线程同时访问且想修改sum),所以这里提前要用到下面的互斥量和锁来实现

#include
#include
#include
#include
#include
using namespace std;

static const int MAX=10e7;
static double sum=0;
static mutex mtx;

void worker(int min,int max){
    for(int i=min;i(end_time-start_time).count();
    cout<<"cost :"< threads;
    sum=0;
    min=0;
    auto start_time=std::chrono::steady_clock::now();
    for(int t=0;t(end_time-start_time).count();
    cout<<"cost :"<#include
#include
#include

using namespace std;

int v=0;
std::mutex v_mutex;


void incre(){
    std::lock_guard lock(v_mutex);
    ++v;
    std::cout< 

使用lock_guard自动为互斥量加锁与解锁,在构造的时候就自动加锁,在生命周期结束的时候自动解锁.

对于上面的几种锁,如果要锁住多个互斥量可以这样写

  • lock_guard

​

lock(mutex1,mutex2);
lock_guard lock1(mutex1,adopt_lock);
lock_guard lock2(mutex2,adopt_lock);
  • unique_lock

​

unique_lock lock1(mutex1,defer_lock);
unique_lock lock2(mutex2,defer_lock);
lock(mutex1,mutex2);
  • scoped_lock
scoped_lock lockAll(mutex1,mutex2);
条件变量

更常见的情况是当满足某个条件时才继续执行,否则就等待.这个时候就需要使用条件变量condition_variable,它得配合unique_lock一起使用.

#include
#include
#include

using namespace std;

int v=0;
static mutex mtx;

void change(int change_v){
    std::unique_lock lock(mtx);
    v=change_v;
    cout< 

配合上条件变量我们就可以写一个生产者和消费者的模型

#include
#include
#include
#include
#include
#include


using namespace std;

int main(){
    queue produced;
    mutex mtx;
    condition_variable cond;
    bool notified=false;

    auto producer=[&](){
        for(int i=0;;i++){
            std::this_thread::sleep_for(std::chrono::milliseconds(900));
            std::unique_lock lock(mtx);
            //生产
            cout<<"Producing:"< lock(mtx);
            while(!notified){
                cond.wait(lock);
            }
            lock.unlock();
            //消费
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            lock.lock();
            while(!produced.empty()){
                cout<<"Consuming..."<#include
#include
#include
#include
#include

using namespace std;

static const int MAX=10e8;
static double sum=0;

void worker(int min,int max){
    cout<<"The Thread ID is:"<#include
#include
#include

using namespace std;

int main(){
    std::packaged_tasktask([](){return 111;});

    std::future result=task.get_future();
    std::thread(std::move(task)).detach();
    cout<<"Waiting ..."< 

这里用一个线程去得到返回值,然后当我们需要的时候使用get方法就可以得到结果

当然还有一些更进阶的使用方式,当我们需要某个函数计算结果并且将任务结束和结果返回分离开这个时候还需要使用promise搭配future使用

#include
#include
#include
#include
#include

using namespace std;

static int MAX=10e7;

double concurrent_worker(int min, int max) {
  double sum = 0;
  for (int i = min; i <= max; i++) {
    sum += sqrt(i);
  }
  return sum;
}

void concurrent_task(int min, int max, promise* result) {
  vector> results;

  unsigned concurrent_count = thread::hardware_concurrency();
  min = 0;
  for (int i = 0; i < concurrent_count; i++) {
    packaged_task task(concurrent_worker);
    results.push_back(task.get_future()); 

    int range = max / concurrent_count * (i + 1);
    thread t(std::move(task), min, range);
    t.detach();

    min = range + 1;
  }

  cout << "threads create finish" << endl;
  double sum = 0;
  for (auto& r : results) {
    sum += r.get();
  }
  result->set_value(sum);
  cout << "concurrent_task finish" << endl;
}

int main() {
  auto start_time = chrono::steady_clock::now();

  promise sum;
  concurrent_task(0, MAX, &sum);

  auto end_time = chrono::steady_clock::now();
  auto ms = chrono::duration_cast(end_time - start_time).count();
  cout << "Concurrent task finish, " << ms << " ms consumed." << endl;
  cout << "Result: " << sum.get_future().get() << endl;
  return 0;
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/948332.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号