C++ 标准库:多线程

【链接】cplusplus.com/reference/multithreading/

    C++ 标准库提供了丰富的多线程支持,主要集中在 <thread>, <mutex>, <condition_variable>, 和 <future> 这些头文件中。

  1. std::thread类:用于创建和管理线程的对象。
  2. std::mutex类:用于实现互斥访问,保护共享资源的完整性。
  3. std::condition_variable类:用于线程间的条件同步。
  4. std::atomic模板类:用于实现原子操作,确保数据的原子性

线程管理

<thread>:用于创建和管理线程。使用 std::thread 类来创建线程。std::thread 的构造函数可以接受一个可调用对象(如函数、lambda表达式、函数对象等)以及该可调用对象所需的参数

#include <iostream>
#include <thread>

void threadFunction(int x) {
    std::cout << "Hello from thread with argument: " << x << std::endl;
}

int main() {
    std::thread t(threadFunction, 42); // 创建并启动线程
    t.join(); // 等待线程结束
    return 0;
}
int main() {
    std::thread t([](){
        std::cout << "Hello from a thread!" << std::endl;
    });
    t.join(); // 等待线程结束
    return 0;
}

        在C++中使用多线程时,除了直接传递函数指针给 std::thread 来创建新线程外,还可以使用函数对象(即实现了 operator() 的类实例)。函数对象不仅允许你传递参数给线程函数,还能保持状态,这为编写更复杂的并发程序提供了极大的灵活性 

#include <iostream>
#include <thread>

//带有状态的函数对象: 这个函数对象在构造时接收一个参数,并在被调用时打印该参数

class PrintTask {
private:
    int id;
public:
    PrintTask(int thread_id) : id(thread_id) {} // 构造函数初始化id
    
    void operator()() const {
        std::cout << "Thread ID " << id << " is running.\n";
    }
};

int main() {
    PrintTask task1(1), task2(2);
    std::thread t1(task1), t2(task2); // 创建两个线程,每个都有自己的任务和ID
    t1.join();
    t2.join();
    return 0;
}

std::thread 提供了一些方法来管理线程:

  • join():等待线程结束。如果主线程调用了 join(),它将阻塞,直到与之关联的线程执行完毕。

  • detach():将线程与 std::thread 对象分离,允许线程独立执行。一旦分离,std::thread 对象就不能再用来调用 join() 或 detach()

  • get_id():返回线程的ID。

  • swap():交换两个 std::thread 对象的状态。

  • native_handle():返回底层实现特定的句柄。通常用于与平台相关的API交互。

互斥量

<mutex>:提供互斥量类型以保护共享数据不被同时访问

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 定义一个互斥量

void printBlock(int n, char c) {
    std::lock_guard<std::mutex> lock(mtx); // 自动管理锁的获取和释放
    for (int i = 0; i<n; ++i) { 
        std::cout << c; 
    }
    std::cout << '\n';
}

int main(){
    std::thread th1(printBlock, 50, '*');
    std::thread th2(printBlock, 50, '$');

    th1.join();
    th2.join();

    return 0;
}

条件变量

<condition_variable>:允许线程等待某个条件变为真, std::condition_variable是C++标准库中的一个类,用于在多线程编程中实现线程间的条件变量和线程同步。它提供了等待通知的机制,使得线程可以等待某个条件成立时被唤醒,或者在满足某个条件时通知其他等待的线程。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void printId(int id) {
    std::unique_lock<std::mutex> lck(mtx);
    cv.wait(lck, []{return ready;}); // 等待直到ready为true
    std::cout << "Thread " << id << '\n';
}

void go() {
    std::unique_lock<std::mutex> lck(mtx);
    ready = true;
    cv.notify_all(); // 唤醒所有等待的线程
}

int main() {
    std::thread threads[10];
    for (int i = 0; i<10; ++i)
        threads[i] = std::thread(printId,i);

    std::cout << "10 threads ready to race...\n";
    go(); // 触发所有线程继续执行

    for (auto& th : threads) th.join();

    return 0;
}

异步操作

    <future>:用于处理异步任务的结果,std::future 是 C++ 标准库中用于处理异步操作结果的一个模板类,它与 std::async、std::promise 和 std::packaged_task 一起工作,提供了一种方便的方式来获取异步任务的执行结果。通过 std::future,可以在一个地方启动异步操作,并在另一个地方获取该操作的结果。

std::async: 启动一个异步任务并返回一个 std::future 对象。
std::future: 提供对异步结果的访问。
std::promise: 允许在某一时刻设置共享状态的值,然后通过关联的 std::future 对象来获取这个值。
std::packaged_task: 将可调用对象(如函数、lambda表达式或函数对象)包装成一个异步任务,并且可以将结果存储在一个 std::future 中。

#include <iostream>
#include <future>
#include <chrono>

// 模拟耗时操作的函数
int longComputation(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟延迟
    return x * x;
}

int main() {
    // 使用 std::async 启动异步任务,并获得一个 std::future 对象
    std::future<int> result = std::async(std::launch::async, longComputation, 5);

    std::cout << "Doing other work...\n";
    
    // 调用 get 方法等待任务完成并获取结果
    int value = result.get();
    std::cout << "The result is: " << value << "\n"; // 输出:The result is: 25
    
    return 0;
}

使用 std::promise 设置值,并通过 std::future 获取该值

#include <iostream>
#include <thread>
#include <future>

void modifyValue(std::promise<int>&& p) {
    int newValue = 42;
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟一些工作
    p.set_value(newValue); // 设置共享状态的值
}

int main() {
    std::promise<int> p;
    std::future<int> f = p.get_future(); // 获取与 promise 相关联的 future

    std::thread t(modifyValue, std::move(p));

    // 等待 future 变得可用,并获取结果
    int value = f.get();
    std::cout << "Value: " << value << "\n"; // 输出:Value: 42

    t.join();

    return 0;
}

std::packaged_task 包装了一个可调用对象,使得它可以被调用多次,并且每次调用都可以生成一个新的 std::future 对象。

#include <iostream>
#include <future>
#include <thread>

int compute(int a, int b) {
    return a + b;
}

int main() {
    std::packaged_task<int(int, int)> task(compute); // 创建 packaged_task
    std::future<int> result = task.get_future(); // 获取 future

    std::thread t(std::move(task), 2, 3); // 移动 task 到新线程中

    // 获取异步操作的结果
    std::cout << "Result: " << result.get() << "\n"; // 输出:Result: 5

    t.join();

    return 0;
}

原子操作

std::atomic 是 C++11 引入的标准库模板类,用于提供原子操作支持。它允许你安全地对单个对象进行读写操作,即使在多线程环境下也是如此,而无需使用互斥锁等同步机制。原子操作是不可分割的操作,意味着它们不会被线程切换或中断打断,这使得它们非常适合用于实现无锁数据结构或简单的同步原语。

原子性: 操作要么完全执行,要么完全不执行,不会有中间状态。
内存顺序: 控制编译器和CPU如何优化指令的顺序,以保证多线程环境下的正确性。C++ 提供了几种内存顺序模型,包括 memory_order_relaxed, memory_order_acquire, memory_order_release, memory_order_acq_rel, 和 memory_order_seq_cst(默认)。

std::atomic 提供了一种简单而有效的方法来确保共享数据的线程安全性。通过选择适当的内存顺序,可以根据性能需求和程序正确性要求调整原子操作的行为。虽然std::atomic 非常强大,但在设计复杂的并发程序时,仍需谨慎考虑死锁、活跃性以及其他并发相关的问题。

简单计数器

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> counter(0); // 定义一个原子类型的计数器

void incrementCounter() {
    for (int i = 0; i < 1000; ++i) {
        ++counter; // 原子递增操作
    }
}

int main() {
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << "\n"; // 输出:Final counter value: 2000
}

内存顺序

 使用不同的内存顺序来控制原子操作的行为。这里使用了 memory_order_relaxed,这意味着没有同步或顺序约束,适用于不需要同步其他变量的情况

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

std::atomic<bool> ready(false);
std::atomic<int> data(0);

void producer() {
    data.store(42, std::memory_order_relaxed); // 存储值,但不与其它操作同步
    ready.store(true, std::memory_order_release); // 发布操作,通知消费者可以继续
}

void consumer() {
    while (!ready.load(std::memory_order_acquire)); // 等待直到ready变为true
    // 当我们到达这里时,我们知道data已经被生产者设置好了
    std::cout << "Data is " << data.load(std::memory_order_relaxed) << "\n";
}

int main() {
    std::thread p(producer);
    std::thread c(consumer);

    p.join();
    c.join();

    return 0;
}

        在这个例子中,memory_order_acquire 和 memory_order_release 确保了在消费者看到 ready 变为 true 后,能够安全地读取 data 的值。

使用自定义类型

        虽然 std::atomic 最常用于基本类型,但它也可以用于自定义类型,只要这些类型满足可复制构造、可复制赋值、非浮点类型以及具有平凡的构造函数和析构函数等条件。

#include <atomic>
#include <iostream>

struct Point {
    int x, y;
};

int main() {
    std::atomic<Point> atomicPoint({0, 0});
    Point initial = atomicPoint.load(); // 加载原子值
    std::cout << "Initial point: (" << initial.x << ", " << initial.y << ")\n";

    Point newValue = {5, 10};
    atomicPoint.store(newValue); // 存储新值

    Point current = atomicPoint.load();
    std::cout << "Updated point: (" << current.x << ", " << current.y << ")\n";

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值