C++多线程编程

线程的一些基本概念

  1. 线程阻塞
    线程阻塞是一种状态,可以通过多种方式实现阻塞(sleep_for(), condition_variable)。线程阻塞并不是单纯的指线程程序停在某一位置不向下执行,而是一种操作系统支持的高效线程调度机制,线程阻塞状态发生后不再参与竞争cpu时间片,直到某种事件发生结束阻塞状态。所以阻塞状态的线程不会消耗系统资源,为了区分这里列出两种能使线程暂停但是并没有阻塞线程的例子:
    // 此段代码演示子线程里while(condition)实现的线程暂停,这种方法会耗尽cpu的一个硬件核心。
    // 属于非常不合理的编程方式,应该禁止使用
    #include <iostream>
    #include <thread>
    
    using namespace std;
    
    bool pause_flg = true; // 全局状态标志
    
    [[noreturn]] void do_work()	// [[noreturn]]是状态变量,告诉编译器此函数永远不会返回
    {
        while (pause_flg) {
        // this_thread::yield();
                ;
        }
        cout << "Subthread is running!" << endl;
    }
    
    int main()
    {
        thread trd_1(do_work);
        cout << "Mian thread is running!" << endl;
        this_thread::sleep_for(chrono::seconds(20)); // 等待20s,此刻cpu的一个核心将满载
        pause_flg = false;
        trd_1.join();
        return 0;
    }
    
    上面的例子while()循环里是一个空循环,导致了cpu的不合理消耗。可能有人会想到在while()中使用this_thread::yield();交出cpu的使用权,以为这样可以避免cpu消耗。其实是错误的,把上面的代码修改一下:
    while (pause_flg) {
    	// 反注释这一行
    	this_thread::yield(); // 放弃cpu的此次执行权
        //   ;
    }
    
    这样发现cpu还是满载一个核心,这是因为this_thread::yield();结束后我们的线程还是保持资源竞争权力的,系统就会立刻重新给我们的线程分配资源,然后我们的线程又放弃执行权,如此反复,cpu也就在频繁的线程调度中消耗资源。
    【正确的线程阻塞方式】
    应该用condition_variable条件变量,参考:C++11 并发指南五(std::condition_variable 详解)
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    
    using namespace std;
    
    std::mutex mtx; // 全局互斥锁.
    std::condition_variable cv; // 全局条件变量.
    
    void do_work()
    {
        unique_lock<mutex> lck(mtx);
        cv.wait(lck);
        cout << "Subthread is running!" << endl;
    }
    
    int main()
    {
        thread trd_1(do_work);
        cout << "Mian thread is running!" << endl;
        this_thread::sleep_for(chrono::seconds(20));    // trd_1线程通过cv.wait(lck)挂起,所以不会占用cpu资源
        cv.notify_all();
        trd_1.join();
        return 0;
    }
    

std::thread类

std::thread类是c++11引入标准库的。使得C++编写跨平台多线程程序更加方便。下面介绍一下基本用法。

构造函数

待写。。。

常用功能

线程池

一个简易的线程池实现:

// file name: ThreadPool.hpp
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <thread>
#include <utility>
#include <vector>

class ThreadPool {
private:
    // 线程池最大线程数量
    const uint32_t MAX_THREAD_NUM = 256;

    void looprun()
    {
        std::function<void()> func; //定义基础函数类func
        bool if_success {false}; // 是否成功取出任务

        //判断线程池是否关闭,没有关闭,循环提取
        while (!this->m_shutdown)
        {
            {
                std::unique_lock<std::mutex> lock(this->m_conditional_mutex);   // 加锁
                //如果任务队列为空,阻塞当前线程
                if (this->tasks.empty()) {
                    this->m_conditional_lock.wait(lock); //等待条件变量通知,开启线程
                }
                if (this->m_shutdown)
                    return;
                //取出任务队列中的元素
                if (!tasks.empty()) {
                    func = std::move(this->tasks.front()); // 取一个 task
                    this->tasks.pop();
                    if_success = true;
                } else{
                    if_success = false;
                }
            }
            //如果成功取出,执行工作函数
            if (if_success) {
                func();
            }
        }
    }

    std::atomic<bool> m_shutdown; // 线程池是否关闭
    std::queue<std::function<void()>> tasks; // 任务队列, std::queue线程不安全,操作需要加锁
    std::vector<std::thread> threads; // 工作线程队列
    std::mutex m_conditional_mutex; // 线程休眠锁互斥变量
    std::condition_variable m_conditional_lock; // 线程环境锁,让线程可以处于休眠或者唤醒状态

public:
    //线程池构造函数
    ThreadPool(const int n_threads) : threads(std::vector<std::thread>(n_threads)), m_shutdown(false)
    {
        if (n_threads > MAX_THREAD_NUM) { std::cout << "警告: 线程池过大,建议重新构造线程池!" << std::endl; }

        for (int i = 0; i < threads.size(); ++i) {
            threads[i] = std::thread([this](){ this->looprun(); });     //分配工作线程
        }
    }

    ThreadPool(const ThreadPool &) = delete; //拷贝构造函数,并且取消默认父类构造函数
    ThreadPool(ThreadPool &&) = default; // 拷贝构造函数,允许右值引用

    ThreadPool & operator=(const ThreadPool &) = delete; // 赋值操作
    ThreadPool & operator=(ThreadPool &&) = delete; //赋值操作

    ~ThreadPool()
    {
        m_shutdown = true;
        m_conditional_lock.notify_all(); //通知 唤醒所有工作线程
        // Waits until threads finish their current task and shutdowns the pool
        for (int i = 0; i < threads.size(); ++i) {
            if(threads[i].joinable()) { //判断线程是否正在等待
                threads[i].join();  //将线程加入等待队列
            }
        }
    }

    /*! Submit a function to be executed asynchronously by the pool
     * 不支持直接接受类非静态成员函数。有三种方法可以实现接受类非静态成员函数:
     *      1. submit([&](){ dog.sayHello(); });
     *      2. submit(std::bind(&Dog::sayHello, &dog));
     *      3. submit(std::mem_fn(&Dog::sayHello), &dog);
     *
     * @tparam F
     * @tparam Args
     * @param f
     * @param args
     * @return
     */
    template<typename F, typename...Args>
    auto submit(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
        // Create a function with bounded parameters ready to execute
        // 绑定函数和实参
        std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
        // Encapsulate it into a shared ptr in order to be able to copy construct / assign
        auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);
        // Wrap packaged task into void function
        std::function<void()> wrapper_func = [task_ptr]() {
            (*task_ptr)();
        };

        {
            std::unique_lock<std::mutex> lock(m_conditional_mutex);   // 加锁
            tasks.emplace(wrapper_func);    // 压入队列
        }
        // 唤醒一个等待中的线程
        m_conditional_lock.notify_one();
        // 返回先前注册的任务指针
        return task_ptr->get_future();
    }

};

如何使用:

#include <iostream>
#include "string"
#include <random>
#include "ThreadPool.hpp"

using namespace std;

// 全局锁
std::mutex  mtx;

// 不返回结果
void print_int(int a)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    unique_lock<mutex> lock(mtx);   // 加锁防止打印错乱
    cout << "a: " << a << endl;
}

// 直接返回结果
int test_return(int a, int b)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    return a + b;
}

// 通过引用和return返回结果
int test_ref(int a, int& ref_1, int& ref_2)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    ref_1 = a + 100;
    ref_2 = a + 1000;
    return a + 10000;
}

int main()
{
    // 创建3个线程的线程池
    ThreadPool pool(3);

    // 提交10个不返回结果的任务
    for (int i = 0; i < 10; i++) {
        pool.submit(print_int, i);
    }
    this_thread::sleep_for(chrono::seconds(5)); // 等待所有任务完成

    // 提交带return的函数
    auto future1 = pool.submit(test_return, 10, 20);
    // 等待任务完成,并取出结果
    int res = future1.get();
    std::cout << "Last operation result is equals to: " << res << std::endl;

    // 通过引用和return返回结果
    int output_ref_1, output_ref_2;
    auto future2 = pool.submit(test_ref, 8, std::ref(output_ref_1),  std::ref(output_ref_2));
    // 等待任务完成
    int out = future2.get();
    std::cout << "output_ref_1: " << output_ref_1 << "    output_ref_2: " << output_ref_2 << "    output_return: " << out << std::endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值