Bootstrap

C++ 11重点总结2

1C++11多线程

主要对少用的知识点进行详细描述,用的多的就给示例。照猫画虎

#include<thread>
// 默认构造函数
thread() 
// 初始化构造函数
template<class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args)
// 移动构造函数
thread(thread&& x) noexcept
//主要API:
// 获取线程ID
get_id()  
// 判断线程是否可join
joinable()  
// 等待线程执行完成
join()  
// 分离线程
detach()
// 创建类成员函数线程
class A {
public:
    static void fun3(int a) {
        cout << a << endl;
    }
};
std::thread t3(&A::fun3, 1);

互斥量

C++11提供如下4种语义的互斥量(mutex)和RAII(通过类的构造析构)来实现更好的编码方式。

std::mutex,独占的互斥量,不能递归使用。

// 特点
- 最基本的互斥量
- 不支持递归锁定
- 同一线程重复锁定会导致死锁

// 主要方法
void lock();      // 加锁
void unlock();    // 解锁
bool try_lock(); // 尝试加锁

std::time_mutex,带超时的独占互斥量,不能递归使用。

// 特点
- 支持超时锁定
- 不支持递归锁定

// 额外方法
bool try_lock_for(duration);   // 在指定时间内尝试锁定
bool try_lock_until(时间点);   // 在指定时间点前尝试锁定

std::recursive_mutex,递归互斥量,不带超时功能。

// 特点
- 允许同一线程多次获取锁
- 需要相应次数的解锁
- 不带超时功能

// 使用场景
- 递归调用需要多次加锁的场景
- 不建议过度使用

std::recursive_timed_mutex,带超时的递归互斥量。

// 特点
- 结合递归锁和超时特性
- 同一线程可多次获取锁
- 支持超时锁定

// 方法
bool try_lock_for(duration);
bool try_lock_until(时间点);

选择建议:

  1. 优先使用std::mutex
  2. 需要超时机制时使用std::timed_mutex
  3. 尽量避免递归锁
  4. 复杂场景可考虑std::recursive_timed_mutex

lock_guard和unique_lock的使用和区别

  1. 选择建议
  • 简单场景:使用lock_guard
  • 复杂场景:使用unique_lock
  • 需要更精细锁控制:推荐unique_lock
lock_guard:
- 自动管理
- 不可手动控制
- 轻量级
- 简单安全

unique_lock:
- 可手动控制
- 支持延迟锁定
- 可转移所有权
- 更灵活

条件变量

// 作用
- 线程间同步机制
- 允许线程等待特定条件
- 比轮询更高效

// 主要类
std::condition_variable
std::condition_variable_any

核心方法
// 等待方法 1释放 2谓语判断 3挂起等唤醒 4.唤醒竞争锁。
void wait(unique_lock<mutex>& lock);

// 带谓词的等待
template<class Predicate>
void wait(unique_lock<mutex>& lock, Predicate pred);
//谓语参数2 等待 时间点 返回true
void wait_until(unique_lock<mutex>& lock,const time_point& absolute_time);
//谓语参数2 等待 时间段 返回true
void wait_for(unique_lock<mutex>& lock,const duration& relative_time);

// 通知方法
void notify_one();     // 唤醒一个等待线程
void notify_all();     // 唤醒所有等待线程

注意事项

  • 总是使用unique_lock
  • 防止虚假唤醒(使用谓词)
  • 注意死锁风险
  • 适当处理异常

使用建议

  • 替代轮询
  • 提高线程同步效率
  • 配合互斥量使用
  • 谨慎设计等待条件

 原子变量

#include <atomic>

// 基本原子类型
std::atomic<int> atomic_int;
std::atomic<bool> atomic_bool;
std::atomic<long> atomic_long;
// 内存序类型
enum memory_order {
memory_order_relaxed,  // 最宽松
memory_order_consume,
memory_order_acquire,  // 获取语义
memory_order_release,  // 释放语义
memory_order_acq_rel, // 获取释放
memory_order_seq_cst   // 顺序一致性(默认)
};

核心操作:
// 基本操作
void store(T value);     // 存储
T load() const;          // 加载
T exchange(T value);     // 替换并返回旧值

// 比较交换
bool compare_exchange_weak(T& expected, T desired);
bool compare_exchange_strong(T& expected, T desired);

关键点总结:

  • 提供无锁线程安全操作
  • 开销比互斥量低
  • 适合简单类型
  • 灵活的内存序控制
  • 主要用于轻量同步

选择建议:

  • 简单同步:原子变量
  • 复杂同步:互斥量
  • 性能关键:无锁编程

call_once 和 once_flag

#include <mutex>
#include <thread>

std::once_flag flag;

void init_function() {
    // 仅执行一次的初始化代码 多线程安全
    std::call_once(flag, [](){
        // 初始化逻辑
        std::cout << "Initialization" << std::endl;
    });
}
void thread_func() {
    init_function(); 
}

关键点总结:

  • 线程安全的一次性初始化
  • 避免重复初始化
  • 支持异常处理
  • 性能开销低
  • 适合单例、资源初始化

使用建议:

  • 替代传统的加锁初始化
  • 简化多线程初始化逻辑
  • 适用于静态初始化
  • 处理复杂初始化场景

2 function和bind用法

关键点总结:

  • std::function:类型擦除的可调用对象包装器
  • std::bind:参数绑定和函数适配
  • 支持多种可调用对象
  • 提供灵活的回调机制
  • 有一定性能开销
  • class FunctionPerformance {
    public:
        // 性能注意事项
        void performanceConsiderations() {
            // 尽量避免频繁创建function对象
            // 推荐:
            std::function<int(int)> cached_func = [](int x) { return x * x; };
            
            // 不推荐:每次都创建新的function对象
            for (int i = 0; i < 1000; ++i) {
                std::function<int(int)> temp_func = [](int x) { return x * x; };
                // 性能开销较大
            }
        }
    
        // 类型擦除的开销
        void typeErasureOverhead() {
            // function会有一定的性能开销
            // 对于性能敏感场景,考虑直接使用模板
            
            // 开销较大的方式
            std::function<int(int)> func = [](int x) { return x * x; };
            
            // 轻量级模板方式
            auto template_func = [](int x) { return x * x; };
        }
    };

最佳实践:

  • 适度使用,注意性能
  • 优先使用模板
  • 谨慎处理生命周期
  • 避免过度复杂的绑定

使用建议:

  • 简单场景:直接使用lambda
  • 复杂参数:使用bind
  • 类型无关:使用function
  • 性能敏感:使用模板

3 可变模板参数

允许定义接受任意数量和类型参数的模板函数和模板类。

基本语法

template<typename... Args>

可变参数函数

// 打印所有参数
template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << std::endl;
}

// 使用
print(1, 2, 3, "hello"); // 可以接受不同类型和数量的参数

template<typename... Args>
void forwardAll(Args&&... args) {
    // 完美转发所有参数
    someFunction(std::forward<Args>(args)...);
}

// 基础情况
void print() {
    std::cout << std::endl;
}

// 递归展开
template<typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";
    print(rest...); // 递归调用
}

折叠表达式(C++17)

template<typename... Args>
auto sum(Args... args) {
    return (args + ...); // 从左到右求和
}

// 使用示例
int result = sum(1, 2, 3, 4, 5); // 结果为15

简单的编译期求和

template<typename... Args>
constexpr auto compile_sum(Args... args) {
    return (args + ...);
}

int main() {
    constexpr int result = compile_sum(1, 2, 3, 4);
    static_assert(result == 10, "Compile-time sum failed");
    return 0;
}

注意事项:

  • 可变模板参数需要小心处理,通常使用递归或折叠表达式展开
  • C++17的折叠表达式大大简化了参数包的处理
  • 可以与其他模板技术结合,如完美转发、类型萃取等

4 异步操作

#include <future>
#include <thread>

// 四者关系
// promise -> future (异步传递结果)
// async -> future (简化异步操作)
// packaged_task -> future (可调用对象的异步封装)

// 推荐:
- 使用async简化异步编程
- 合理处理异常
- 注意future的生命周期
- 避免过度创建线程

// 不推荐:
- 频繁创建future
- 忽略异常处理
- 长时间阻塞get()
  • std::future:

    std::future 是一个容器类,它保存了一个异步操作的结果。
    它提供了一种访问异步操作结果的方式,可以等待操作完成或检查其是否就绪。接收return值
    std::future 可以从 std::promise ,async std::packaged_task 获得。

  • 常用方法:
    get(): 等待异步任务完成并返回结果。
    wait(): 等待异步任务完成,但不获取结果。
    valid(): 检查 std::future 对象是否有效。
    wait_for(timeout): 等待指定时间内异步任务完成。
  • #include <iostream>
    #include <future>
    #include <thread>
    #include <chrono>
    
    int asyncFunction() {
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
        return 42;
    }
    
    int main() {
    
        std::future<decltype (asyncFunction())> result = std::async(std::launch::async, asyncFunction);
        std::cout << "Doing other work..." << std::endl;
    
        // 这里可以执行其他工作,asyncFunction()会在后台执行
    
        int value = result.get(); // 获取结果,如果操作尚未完成,这里会阻塞
        std::cout << "The answer is " << value << std::endl;
        return 0;
    }

  • std::async:

    • std::async 是一个函数,它启动一个异步操作并返回一个 std::future 来保存结果
    • 它提供了一种简单的方式来异步执行一个函数并获取其结果。
    • std::async 可以使用不同的启动策略,
      •  
        policy: 一个 std::launch 类型的枚举值,用于指定函数的启动策略。可选值包括
        std::launch::defered表明该函数会被延迟调用,直到在future上调用get()或者wait()为止
        std::launch::async,表明函数会新创线程调用
        
        f: 要异步执行的函数对象。可以是普通函数、lambda 表达式或成员函数。
        args: 要传递给函数 f 的参数。
        
        template <class Function, class... Args>
        [[nodiscard]] std::future<std::invoke_result_t<std::decay_t<Function>, std::decay_t<Args>...>>
        async(std::launch policy, Function&& f, Args&&... args);
        
        MyClass obj;
        auto result3 = std::async(std::launch::async, &MyClass::doSomething, &obj, 5, 10);

  • std::promise:

    • std::promise 是一个容器类,它保存了一个可以异步设置的值。
    • 它用于向 std::future 对象提供一个值,通常来自另一个线程或异步操作。
    • std::promise 通常与 std::future 一起使用,实现并发编程中的生产者-消费者模式。
      void promise_example() {
          // promise用于在线程间传递单一值
          std::promise<int> promise;
          std::future<int> future = promise.get_future();
      
          // 在另一个线程中设置值
          std::thread t([&promise](){
              try {
                  // 设置值
                  promise.set_value(42);
                  
                  // 或设置异常
                  // promise.set_exception(std::current_exception());
              } catch(...) {
                  promise.set_exception(std::current_exception());
              }
          });
      
          // 主线程获取值
          try {
              int value = future.get();
              std::cout << "Received: " << value << std::endl;
          } catch (const std::exception& e) {
              std::cout << "Exception: " << e.what() << std::endl;
          }
      
          t.join();
      }

  • std::packaged_task:

    • std::packaged_task 是一个包装可调用对象(如函数或 lambda 表达式)的类,可以异步执行该对象。
    • 它提供了一种将 std::future 与异步操作的结果关联起来的方式。
    • std::packaged_task 在你想异步执行一个函数并获取其结果,但不想手动创建和管理 std::promisestd::future 时很有用。
    • void packaged_task_example() {
          // 将可调用对象包装为异步任务
          std::packaged_task<int(int,int)> task([](int a, int b) {
              return a + b;
          });
      
          // 获取future
          std::future<int> future = task.get_future();
      
          // 方法1:直接调用
          task(10, 20);
          std::cout << "Result: " << future.get() << std::endl;
      
          // 方法2:在线程中执行
          std::thread t(std::move(task), 30, 40);
          t.join();
      }

关键特点:

  • future:获取异步结果
  • promise:设置异步结果
  • async:简化异步任务
  • packaged_task:可调用对象异步包装

选择建议:

  • 简单异步:std::async
  • 复杂控制:std::promise + std::future
  • 可调用对象:std::packaged_task

综合示例简单的线程池

#include <iostream>
#include <future>
#include <queue>
#include <thread>
#include <vector>
#include <functional>
#include <condition_variable>

class ThreadPool {
private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop = false;

public:
    ThreadPool(size_t threads) {
        for (size_t i = 0; i < threads; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(queue_mutex);
                        condition.wait(lock, [this] {
                            return stop || !tasks.empty();
                            });

                        if (stop && tasks.empty()) return;

                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
                });
        }
    }

    template<class F, class... Args>
    auto enqueue(F&& task, Args&&... args)
        -> std::future<std::invoke_result_t<F, Args...>>
    {
        using return_type = std::invoke_result_t<F, Args...>;

        // 使用 std::packaged_task 包装任务
        auto packaged_task =
            std::make_shared<std::packaged_task<return_type()>>(
                std::bind(std::forward<F>(task), std::forward<Args>(args)...)
            );

        std::future<return_type> future = packaged_task->get_future();

        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            tasks.emplace([packaged_task]() {
                (*packaged_task)();
                });
        }

        condition.notify_one();
        return future;
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for (auto& worker : workers) {
            worker.join();
        }
    }
};

// 使用示例
void async_thread_pool_demo() {
    ThreadPool pool(4);

    // 测试不同类型的任务
    auto future1 = pool.enqueue([]() {
        return 1;
        });

    auto future2 = pool.enqueue([](int x) {
        return x + 2;
        }, 3);

    auto future3 = pool.enqueue([]() {
        return std::string("hello");
        });

    // 多种类型的结果获取
    std::cout << future1.get() << std::endl;             // 输出 1
    std::cout << future2.get() << std::endl;             // 输出 5
    std::cout << future3.get() << std::endl;             // 输出 hello
}

int main() {
    async_thread_pool_demo();
    return 0;
}

学习资料分享

0voice · GitHub

;