C++并发与多线程

文章目录

  • 引言
  • [1. 未实现多线程互斥访问](#1. 未实现多线程互斥访问)
  • [2. std::mutex实现互斥访问](#2. std::mutex实现互斥访问)
  • [3. condition_variable_any与mutex配合实现互斥访问](#3. condition_variable_any与mutex配合实现互斥访问)
  • [4. async,future与shared_future](#4. async,future与shared_future)
  • [5. atomic](#5. atomic)

引言

本文以生产者消费者模型作为例子,进行多线程技术的探讨。此外,将初步探讨async,future与atomic的使用。

1. 未实现多线程互斥访问

cpp 复制代码
#include<iostream>
#include<thread>
#include<deque>

using namespace std;

// 队列中最大的元素个数
#define MAX_SIZE 100
// 全局变量,用于元素编号计数
int g_count = 0;

// 定义消息队列类,模拟生产者消费者模型
class MQ {
private:
    deque<int> dq;
public:
    void get() {
        while (true) {
            if (!dq.empty()) {
                cout << "消费元素 " << dq.front() << endl;
                dq.pop_front();
            }
        }
    }; 

    void put() {
        while (true) {
            if (dq.size() < MAX_SIZE) {
                cout << "生产元素 " << g_count << endl;
                dq.push_back(g_count ++);
            }
        }
    }

};

// 由于main()函数在后面的章节中不会发生变化,不再展示main()函数代码
int main() {

    MQ mq;
    // 创建两个向队列添加元素的线程
    thread get_thread1(&MQ::get, &mq);
    thread get_thread2(&MQ::get, &mq);
    // 创建两个从队列取出元素的线程
    thread put_thread1(&MQ::put, &mq);
    thread put_thread2(&MQ::put, &mq);

    // 主线程阻塞等待所有子线程执行完毕
    get_thread1.join();
    get_thread2.join();
    put_thread1.join();
    put_thread2.join();
    return 0;
}

运行结果出错

由于main()函数在后面的章节中不会发生变化,将不再展示main()函数代码

2. std::mutex实现互斥访问

cpp 复制代码
#include<iostream>
#include<thread>
#include<mutex>
#include<deque>
using namespace std;

// 队列中最大的元素个数
#define MAX_SIZE 100
// 全局变量,用于元素编号计数
int g_count = 0;

// 定义消息队列类,模拟生产者消费者模型
class MQ {
private:
    deque<int> dq;
    mutex mut;
public:
    void get() {
        while (true) {
            mut.lock(); // 加锁
            if (!dq.empty()) {
                cout << "消费元素 " << dq.front() << endl;
                dq.pop_front();
            }
            mut.unlock(); // 解锁
        }
    }; 

    void put() {
        while (true) {
            mut.lock();
            if (dq.size() < MAX_SIZE) {
                cout << "生产元素 " << g_count << endl;
                dq.push_back(g_count ++);
            }
            mut.unlock();
        }
    }

};

由于get()和put()的执行都需要获得互斥量mut才能执行业务逻辑,那么只要有其中一个线程获得互斥量mut,其他生产者和消费者线程都会阻塞等待互斥量mut的释放,因此可以实现对共享资源,也就是队列的互斥访问。

运行结果正常:

3. condition_variable_any与mutex配合实现互斥访问

cpp 复制代码
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<deque>

using namespace std;

// 队列中最大的元素个数
#define MAX_SIZE 100
// 全局变量,用于元素编号计数
int g_count = 0;

// 定义消息队列类,模拟生产者消费者模型
class MQ {
private:
    deque<int> dq;
    mutex mut;
    condition_variable_any getCond;
    condition_variable_any putCond;
public:
    void get() {
        while (true) {
            mut.lock();
            getCond.wait(mut, [this]{
                return !dq.empty();
            });
            // 在条件变量的唤醒条件中已经添加了!dq.empty()条件
            // 能够执行到这说明队列中一定有元素
            cout << "消费元素 " << dq.front() << endl;
            dq.pop_front();
            // 唤醒一个生产者线程
            putCond.notify_one();
            mut.unlock();
        }
    }; 

    void put() {
        while (true) {
            mut.lock();
            putCond.wait(mut, [this]{
                return dq.size() < MAX_SIZE;
            });
            cout << "生产元素 " << g_count << endl;
            dq.push_back(g_count ++);
            // 唤醒一个消费者线程
            getCond.notify_one();
            mut.unlock();
        }
    }

};

4. async,future与shared_future

cpp 复制代码
std::future<int> res = std::async(std::launch::deferred, myThread1);
// std::launch::deferred 延迟调用
	// 直到调用get()或wait()才会执行函数
	// 并且不会创建新线程,由调用get()或wait()的线程执行
// std::launch::async 创建新线程并立即执行
// std::launch::async | std::launch::deferred 
	// 默认参数,系统自行决定异步或者通过,延迟还是实时

std::thread 创建线程。如果系统资源紧张,程序会报错崩溃。

std::async 创建异步任务。默认情况下,会根据系统的资源情况,决定是否创建新线程执行任务。同时,比较容易拿到入口函数的返回结果。

std::async是否有创建新线程。

cpp 复制代码
std::future<int> result = std::async(myThread1);
std::future_status status = res.wait_for(std::chrono::seconds(0));
// std::future_status status = res.wait_for(0s); // ms s min h
if (status == std::future_status::deferred) {
    // 代表没有创建新线程
} else if (status == std::future_status::timeout || status == std::future_status::ready) {
    // 代表创建了新线程
}

主线程退出前会等待async任务返回,无论async是否创建了新的线程。async返回的future只能get()一次,get()使用的是移动语义。如果需要get()多次,需要将async()返回的future转换成shared_future,shared_future使用的是复制,因此可以get多次。

cpp 复制代码
std::future<int> result = std::async(&threadFun);
// cout << result.get() << endl;
// std::shared_future<int> shared_result = result.share(); // 方式一
// std::shared_future<int> shared_result(std::move(result)); // 方式二
if (result.valid()) { // 是否有效
    std::shared_future<int> shared_result(result.share()); // 方式三
    cout << shared_result.get() << endl;
    cout << shared_result.get() << endl;
    cout << shared_result.get() << endl;
}

5. atomic

普通变量值的写入和读取都不是原子操作,多线程操作可能获取到中间的结果,也就是错误的结果。atomic变量的操作是原子性的,常用于计数或者子线程退出标识。但是,并不是所有对atomic操作都是原子性的。

cpp 复制代码
std::atomic<int> g_atomic_count = 0; // 定义atomic变量
g_atomic_count ++; // 正确
g_atomic_count += 1; // 正确
g_atomic_count = g_atomic_count + 1; // 错误
相关推荐
qeen879 小时前
【算法笔记】前缀和经典题目解析
c语言·c++·笔记·学习·算法
橙子也要努力变强9 小时前
信号捕捉与不可捕捉机制(进阶篇)
linux·服务器·c++
橙子也要努力变强9 小时前
信号的处理方式与生命周期(核心机制篇)
linux·网络·c++
特种加菲猫9 小时前
C++ 容器适配器揭秘:stack, queue 和 priority_queue 的模拟实现
开发语言·c++
郝学胜-神的一滴9 小时前
[力扣 105]二叉树前中后序遍历精讲:原理、实现与二叉树还原
数据结构·c++·算法·leetcode·职场和发展
闻缺陷则喜何志丹9 小时前
【ST表 前缀和】P7809 [JRKSJ R2] 01 序列|普及+
c++·算法·前缀和·洛谷·st表
xh didida9 小时前
C++ --list接口使用及实现
开发语言·c++·list
Wild_Pointer.10 小时前
项目实战:编写CMakeLists管理Qt+OpenCV项目
开发语言·c++·qt
承渊政道10 小时前
【递归、搜索与回溯算法】(floodfill算法:从不会做矩阵题,到真正掌握搜索扩散思想)
数据结构·c++·算法·leetcode·矩阵·dfs·bfs
黎梨梨梨_10 小时前
C++入门基础(上)(namespace和缺省参数)
开发语言·c++