前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学习C++,必须学习的线程知识点

学习C++,必须学习的线程知识点

作者头像
Linux兵工厂
发布2024-04-01 18:09:17
2750
发布2024-04-01 18:09:17
举报
文章被收录于专栏:Linux兵工厂

START

我们都知道自C++11开始引入了线程相关的好多东西。本节我们来学习C++中线程相关的知识点。

1、std::thread

std::thread 是 C++ 标准库中提供的用于创建和管理线程的类。通过 std::thread,可以方便地创建新线程,并在其中执行指定的函数或可调用对象。

以下是 std::thread 的一些重要特点和用法:

  1. 创建线程: 使用 std::thread 可以创建新的线程。可以将函数或可调用对象作为参数传递给 std::thread 构造函数,以在新线程中执行该函数或可调用对象。
  2. 线程管理: std::thread 对象代表一个线程,可以通过该对象来管理线程的状态和行为,如启动线程、等待线程结束、查询线程 ID 等。
  3. 线程同步: 在多线程编程中,通常需要使用同步机制来确保线程间的协调和数据的正确访问。std::thread 可以与其他同步原语(如互斥量、条件变量等)一起使用,实现线程间的同步和通信。
  4. 移动语义: std::thread 支持移动语义,可以通过 std::move 函数将一个 std::thread 对象的所有权转移给另一个对象。这意味着可以将线程对象作为参数传递给函数或存储在容器中。
  5. 线程销毁:std::thread 对象被销毁时,它代表的线程也会被销毁。通常情况下,如果一个 std::thread 对象代表的线程还在运行,会调用 std::terminate 终止程序;如果线程已经结束,会释放线程的资源。

以下是一个示例,展示了如何使用 std::thread 创建新线程并执行函数:

代码语言:javascript
复制
#include <iostream>
#include <thread>

// 线程函数,打印消息
void printMessage() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    // 创建新线程并执行 printMessage 函数
    std::thread t(printMessage);

    // 主线程继续执行其他操作
    std::cout << "Hello from main!" << std::endl;

    // 等待子线程执行完毕
    t.join();

    return 0;
}

在这个示例中,我们通过 std::thread 创建了一个新线程,并将 printMessage 函数作为参数传递给 std::thread 构造函数。在主线程中,我们打印了一条消息,并通过 join 函数等待子线程执行完毕。

通过使用 std::thread,我们可以方便地进行多线程编程,并实现并行执行任务的目的。需要注意的是,在使用 std::thread 时,要确保线程的正确同步和管理,以避免竞态条件和死锁等问题。

2、std::mutex

std::mutex 是 C++ 标准库中提供的互斥量类,用于实现线程之间的互斥访问。互斥量是一种同步原语,用于保护共享资源,确保在任意时刻只有一个线程能够访问该资源,从而避免竞态条件和数据竞争。

以下是 std::mutex 的一些重要特点和用法:

  1. 互斥锁: std::mutex 对象代表一个互斥锁,可以用于保护共享资源。在访问共享资源之前,线程可以使用 std::mutex 对象进行加锁操作,以确保只有一个线程能够访问共享资源。
  2. 加锁和解锁: 使用 std::mutexlock()unlock() 方法可以分别对互斥锁进行加锁和解锁操作。当一个线程对互斥锁进行加锁后,其他线程将无法对同一个互斥锁进行加锁,直到持有该互斥锁的线程将其解锁。
  3. RAII 机制: 通常推荐使用 RAII(Resource Acquisition Is Initialization)机制来管理互斥锁的生命周期。可以使用 std::lock_guardstd::unique_lock 等 RAII 包装类来自动管理互斥锁的加锁和解锁操作,避免忘记手动解锁导致死锁等问题。
  4. 线程安全性: std::mutex 是线程安全的,可以被多个线程同时访问和操作。它提供了基本的互斥保护,但不提供超时、递归锁等高级功能。

以下是一个示例,展示了如何使用 std::mutex 进行线程间的同步:

代码语言:javascript
复制
#include <iostream>
#include <thread>
#include <mutex>

// 共享资源
int counter = 0;
std::mutex mtx; // 互斥锁

// 线程函数,增加 counter 的值
void incrementCounter() {
    // 使用 RAII 机制加锁
    std::lock_guard<std::mutex> lock(mtx);

    // 访问共享资源
    counter++;
}

int main() {
    // 创建多个线程,并执行 incrementCounter 函数
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);

    // 等待线程执行完毕
    t1.join();
    t2.join();

    // 输出结果
    std::cout << "Counter value: " << counter << std::endl;

    return 0;
}

在这个示例中,我们创建了一个共享资源 counter,并用 std::mutex 对象 mtx 来保护它。在 incrementCounter 函数中,我们使用 std::lock_guard 类对互斥锁进行加锁操作,以确保在访问共享资源时只有一个线程能够访问。通过使用 std::mutex,我们可以避免多线程访问共享资源时发生数据竞争的问题。

3、std::lock

std::lock 是 C++11 标准中提供的函数模板,用于在一次操作中对多个互斥量进行加锁操作,以避免死锁和提高程序性能。

std::lock 函数的作用是将多个互斥量对象进行加锁,如果其中任何一个互斥量对象无法加锁(即已被其他线程锁定),则 std::lock 函数会阻塞当前线程,直到所有互斥量对象都被成功加锁。

以下是 std::lock 的一些重要特点和用法:

  1. 原子性加锁: std::lock 函数会原子性地对多个互斥量对象进行加锁操作。这意味着要么所有互斥量都成功加锁,要么所有互斥量都不被加锁,不会出现部分加锁的情况。
  2. 避免死锁: std::lock 函数可以避免死锁的发生。当多个线程需要同时访问多个共享资源时,使用 std::lock 可以确保线程以相同的顺序对互斥量进行加锁,从而避免死锁的发生。
  3. 高效性能: std::lock 函数采用一种高效的算法来对多个互斥量进行加锁操作,因此可以提高程序的性能。相比于分别对每个互斥量进行加锁,使用 std::lock 可以减少线程间的竞争,降低锁的粒度,提高并发性能。
  4. 可变数量的参数: std::lock 函数支持可变数量的参数,可以同时对任意数量的互斥量进行加锁。参数可以是互斥量对象,也可以是指向互斥量对象的指针。

以下是一个示例,展示了如何使用 std::lock 对多个互斥量进行加锁操作:

代码语言:javascript
复制
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx1, mtx2;

void func1() {
    // 使用 std::lock 对两个互斥量进行加锁
    std::lock(mtx1, mtx2);
    
    // 此处对共享资源进行访问

    // 解锁两个互斥量
    mtx1.unlock();
    mtx2.unlock();
}

void func2() {
    // 使用 std::lock 对两个互斥量进行加锁
    std::lock(mtx1, mtx2);
    
    // 此处对共享资源进行访问

    // 解锁两个互斥量
    mtx1.unlock();
    mtx2.unlock();
}

int main() {
    std::thread t1(func1);
    std::thread t2(func2);

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

    return 0;
}

在这个示例中,我们创建了两个互斥量对象 mtx1mtx2,并在两个线程的函数中使用 std::lock 对它们进行加锁操作。std::lock 会确保在一个操作中对两个互斥量进行加锁,避免死锁的发生。完成共享资源的访问后,我们分别对两个互斥量进行解锁操作。

4、std::atomic

std::atomic 是 C++ 标准库中提供的原子类型,用于实现多线程环境下的原子操作。原子操作是不可分割的操作,可以保证在多线程环境下对共享变量的读写操作是线程安全的,即不会发生数据竞争和数据不一致的情况。

std::atomic 提供了一系列原子操作函数,用于对原子类型的对象进行读写、赋值、递增、递减、交换等操作,这些操作是原子的,即线程安全的,不会被其他线程中断。

以下是 std::atomic 的一些重要特点和用法:

  1. 原子类型: std::atomic 可以用于创建原子类型的对象,包括原子整型、原子指针等。原子类型的对象具有原子性,可以在多线程环境下安全地进行读写操作。
  2. 原子操作: std::atomic 提供了一系列原子操作函数,如 load()store()exchange()fetch_add()fetch_sub() 等,用于对原子类型的对象进行读写、赋值、交换、递增、递减等操作。这些操作是原子的,不会被其他线程中断。
  3. 内存顺序: std::atomic 支持不同的内存顺序模型,包括 memory_order_relaxedmemory_order_acquirememory_order_releasememory_order_acq_relmemory_order_seq_cst 等。通过指定不同的内存顺序,可以控制原子操作的执行顺序和可见性。
  4. 原子标志: std::atomic_flagstd::atomic 的特殊类型,用于实现原子的布尔类型。它通常用于实现简单的互斥锁,具有较低的开销和较高的性能。

以下是一个示例,展示了如何使用 std::atomic 进行原子操作:

代码语言:javascript
复制
#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> counter(0); // 原子整型对象

void incrementCounter() {
    for (int i = 0; i < 10000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed); // 原子递增操作
    }
}

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

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

    std::cout << "Counter value: " << counter.load(std::memory_order_relaxed) << std::endl;

    return 0;
}

在这个示例中,我们创建了一个原子整型对象 counter,并用两个线程同时对其进行递增操作。通过使用 std::atomic 提供的原子操作函数 fetch_add(),可以保证对 counter 的递增操作是线程安全的。最后,我们使用 load() 函数读取 counter 的值,确保在输出时能够得到正确的结果。

5、std::call_once

std::call_once 是 C++ 标准库中提供的用于执行只调用一次的函数的函数模板。它可以确保在多线程环境下,某个函数只被调用一次,即使在多个线程中同时调用 std::call_once

std::call_once 的一般形式如下:

代码语言:javascript
复制
template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );

它接受一个 std::once_flag 类型的引用 flag 和一个可调用对象 f,以及可选的参数列表 argsstd::once_flag 是一个用于标记是否已经执行过某个函数的标志。

以下是 std::call_once 的一些重要特点和用法:

  1. 只调用一次: std::call_once 确保传递给它的可调用对象 f 只被执行一次,即使在多个线程中同时调用 std::call_once
  2. 线程安全: std::call_once 是线程安全的,它使用 std::once_flag 来确保在多线程环境下只执行一次。
  3. 延迟初始化: std::call_once 常用于延迟初始化某些资源,确保初始化操作只执行一次,避免竞态条件和资源浪费。
  4. 异常处理: 如果传递给 std::call_once 的可调用对象 f 抛出异常,则 std::call_once 会将异常传递给调用者,而且在下一次调用 std::call_once 时仍然会执行 f

以下是一个示例,展示了如何使用 std::call_once 进行延迟初始化:

代码语言:javascript
复制
#include <iostream>
#include <thread>
#include <mutex>

std::once_flag flag;
int value;

void init_value() {
    std::cout << "Initializing value..." << std::endl;
    value = 42;
}

void get_value() {
    std::call_once(flag, init_value);
    std::cout << "Value is: " << value << std::endl;
}

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

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

    return 0;
}

在这个示例中,我们使用了 std::call_once 来确保 init_value 函数只被执行一次,即使在多个线程中同时调用 get_value 函数。这样可以避免对 value 的多次初始化。在第一次调用 get_value 时,init_value 函数会被执行以初始化 value,而在后续的调用中,init_value 不会再次执行,而是直接获取已经初始化过的 value 值。

6、volatile

在 C++ 中,volatile 是一个关键字,用于告诉编译器对某个变量进行特殊处理,以确保对该变量的读写操作不会被优化器优化掉。volatile 关键字通常用于标识那些可能会被意外修改的变量,比如硬件寄存器、中断服务程序中的共享变量等。

以下是 volatile 关键字的一些特性和用法:

  1. 禁止优化: volatile 告诉编译器对变量的读写操作不能被优化掉,即使这些操作看起来是多余的或者在代码的执行流程中不是必需的。
  2. 禁止重排序: volatile 也可以防止编译器和 CPU 对变量的读写操作进行重排序。这对于多线程编程和与硬件交互的程序很重要,因为这些场景下的操作顺序可能是关键的。
  3. 内存屏障: 在一些架构中,volatile 变量的读写操作会在编译器层面插入内存屏障,以确保对该变量的操作在多线程环境下是按照顺序进行的。
  4. 不保证原子性: volatile 关键字并不保证对变量的操作是原子的。如果需要原子操作,请使用 std::atomic 类型。
  5. 不适用于多线程同步: 尽管 volatile 可以防止编译器的优化,但它并不提供线程同步的机制。在多线程编程中,应该使用互斥量、原子类型等专门的同步机制来保证线程安全。

下面是一个简单的示例,演示了 volatile 的用法:

代码语言:javascript
复制
#include <iostream>

volatile int globalVar = 0;

void foo() {
    while (globalVar == 0) {
        // do something
    }
}

void bar() {
    globalVar = 1;
}

int main() {
    std::thread t1(foo);
    std::thread t2(bar);

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

    return 0;
}

在这个示例中,globalVar 被声明为 volatile int,这告诉编译器对它的读写操作不能被优化掉。因此,foo 函数中的循环会一直等待 globalVar 的值被改变,即使在 bar 函数中修改了它的值。

7、std::condition_variable

std::condition_variable 是 C++ 标准库中提供的用于线程间同步的条件变量类。它配合 std::mutex 使用,用于在多线程环境中实现线程的等待和唤醒机制,允许线程在某个特定条件下进行等待,直到其他线程满足条件后进行唤醒。

以下是 std::condition_variable 的一些重要特点和用法:

  1. 条件变量: std::condition_variable 允许线程在某个特定条件下进行等待,并在条件满足时进行唤醒。它与 std::mutex 配合使用,用于实现线程间的同步和通信。
  2. 等待和唤醒: 线程可以调用 wait() 函数在条件变量上等待,当其他线程调用 notify_one()notify_all() 函数时,等待的线程将被唤醒。notify_one() 用于唤醒单个等待线程,而 notify_all() 用于唤醒所有等待线程。
  3. 互斥锁: 在调用 wait() 函数时,需要传入一个已经加锁的 std::unique_lock<std::mutex> 对象,以确保在等待期间对共享资源的访问是线程安全的。等待期间,std::mutex 会自动释放,允许其他线程对共享资源进行访问。
  4. 超时等待: std::condition_variable 还支持超时等待的功能,可以指定等待的最长时间。如果超过指定的时间仍然没有被唤醒,等待函数会返回,线程可以继续执行其他操作。

以下是一个简单的示例,演示了 std::condition_variable 的基本用法:

代码语言:javascript
复制
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

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

void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return ready; });
    std::cout << "Worker thread is awake." << std::endl;
}

int main() {
    std::thread t(worker);

    // 模拟一些工作
    std::this_thread::sleep_for(std::chrono::seconds(2));

    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();

    t.join();

    return 0;
}

在这个示例中,主线程创建了一个工作线程 t,然后在一段时间后唤醒了该工作线程。工作线程在 cv.wait() 中等待条件变量 readytrue,一旦主线程修改了 ready 的值并调用了 cv.notify_one(),工作线程将被唤醒并继续执行。

8、std::future

std::future 是 C++ 标准库中提供的用于异步任务的类,它用于获取异步操作的结果,或者等待异步操作的完成。std::future 表示一个可能会在将来完成的操作的结果,允许程序在等待异步操作完成时继续执行其他任务。

以下是 std::future 的一些重要特点和用法:

  1. 异步操作: std::future 可以用于表示一个异步操作的结果,允许程序在等待操作完成时继续执行其他任务。异步操作可以通过 std::asyncstd::packaged_taskstd::promise 等方式创建。
  2. 获取结果: 可以通过 get() 函数获取异步操作的结果。如果异步操作尚未完成,调用 get() 函数将会阻塞当前线程,直到异步操作完成并返回结果。
  3. 等待操作完成: 可以使用 wait() 函数等待异步操作完成。wait_for()wait_until() 函数可以用于等待一段时间或者直到特定时间点。
  4. 异常处理: 如果异步操作抛出了异常,std::future 将会保存该异常,并在调用 get() 函数时重新抛出异常。可以使用 std::future::exception() 函数获取异常信息。
  5. 共享状态: std::future 和其相关的类(如 std::promise)共享一个状态,用于表示异步操作的结果。异步操作完成后,std::future 将保存该结果,并提供给调用者。

以下是一个简单的示例,演示了如何使用 std::future 获取异步操作的结果:

代码语言:javascript
复制
#include <iostream>
#include <future>
#include <chrono>

int calculate() {
    // 模拟一个耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

int main() {
    // 创建一个异步任务,并获取其 future 对象
    std::future<int> result = std::async(std::launch::async, calculate);

    // 执行其他任务...
    std::cout << "Waiting for result..." << std::endl;

    // 等待异步操作完成,并获取结果
    int value = result.get();

    std::cout << "Result: " << value << std::endl;

    return 0;
}

在这个示例中,我们使用 std::async 创建了一个异步任务,并将其结果保存在 std::future 对象 result 中。然后,我们执行其他任务,并调用 result.get() 等待异步操作完成并获取结果。一旦异步操作完成,我们就可以从 result 中获取到异步操作的结果。

9、async

std::async 是 C++ 标准库中提供的用于创建异步任务的函数,用于启动一个新的线程或者在线程池中执行指定的任务,并返回一个 std::future 对象,用于获取异步操作的结果。

以下是 std::async 的一些重要特点和用法:

  1. 创建异步任务: std::async 可以用于创建异步任务,执行指定的函数或可调用对象,并返回一个 std::future 对象,用于获取任务的结果。
  2. 执行策略: std::async 支持三种执行策略:std::launch::asyncstd::launch::deferred 和默认策略。std::launch::async 策略表示在新线程或者线程池中执行任务,std::launch::deferred 策略表示延迟执行任务直到调用 get() 函数时,而默认策略由编译器决定。
  3. 返回值类型: std::async 返回一个 std::future 对象,用于获取异步任务的结果。通过 std::future 对象的 get() 函数可以获取任务的结果,该函数会阻塞当前线程直到任务完成并返回结果。
  4. 异常处理: 如果异步任务抛出了异常,std::future 对象将会保存该异常,调用 get() 函数时会重新抛出异常。可以通过 std::future::exception() 函数获取异常信息。
  5. 等待任务完成: 可以通过 std::future::wait() 函数等待异步任务完成,也可以通过 std::future::wait_for()std::future::wait_until() 函数等待一段时间或者直到特定时间点。

以下是一个简单的示例,演示了如何使用 std::async 创建异步任务并获取结果:

代码语言:javascript
复制
#include <iostream>
#include <future>
#include <chrono>

int calculate() {
    // 模拟一个耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

int main() {
    // 创建一个异步任务,并获取其 future 对象
    std::future<int> result = std::async(std::launch::async, calculate);

    // 执行其他任务...
    std::cout << "Waiting for result..." << std::endl;

    // 等待异步操作完成,并获取结果
    int value = result.get();

    std::cout << "Result: " << value << std::endl;

    return 0;
}

在这个示例中,我们使用 std::async 创建了一个异步任务,并将其结果保存在 std::future 对象 result 中。然后,我们执行其他任务,并调用 result.get() 等待异步操作完成并获取结果。一旦异步操作完成,我们就可以从 result 中获取到异步操作的结果。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-03-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux兵工厂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、std::thread
  • 2、std::mutex
  • 3、std::lock
  • 4、std::atomic
  • 5、std::call_once
  • 6、volatile
  • 7、std::condition_variable
  • 8、std::future
  • 9、async
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档