C++线程知识点汇总

2024-04-15 13:42:52 浏览数 (2)

START

Hi,大家好!今天我们来学习一下C 中线程相关的所有知识点。

unsetunsetstd::threadunsetunset

std::thread 是 C 11 标准库中用于创建和管理线程的类,它提供了一种简单的方式来启动新的线程并执行指定的函数或可调用对象。

下面是 std::thread 的主要特点和用法:

  1. 创建线程:使用 std::thread 类可以创建新的线程,并指定要执行的函数或可调用对象。
  2. 并发执行:通过创建多个 std::thread 对象,可以实现多线程并发执行,从而提高程序的性能。
  3. 参数传递:可以将参数传递给线程的执行函数,以便在线程中使用。
  4. 线程管理std::thread 对象可以用于管理线程的生命周期,包括启动线程、等待线程执行完成、加入线程、分离线程等操作。

下面是 std::thread 的基本用法示例:

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

// 执行函数
void hello() {
    std::cout << "Hello, from thread!" << std::endl;
}

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

    // 主线程继续执行其他任务
    std::cout << "Hello, from main thread!" << std::endl;

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

    return 0;
}

以上我们创建了一个新的线程 t,并将 hello 函数作为线程的执行函数。主线程继续执行其他任务,然后调用 t.join() 来等待子线程执行完成。最终输出结果会在子线程和主线程执行完成后一起打印。

要注意的是,在实际开发中,需要注意线程的安全性和正确性,尤其是共享资源的访问问题。使用互斥锁、条件变量等机制可以有效地保护共享资源,避免多线程并发访问导致的问题。

unsetunsetstd::mutexunsetunset

std::mutex 是 C 11 标准库中用于实现互斥锁的类,它提供了一种线程同步的机制,用于保护共享资源的访问,防止多个线程同时访问造成的数据竞争和不一致性。

下面是 std::mutex 的主要特点和用法:

  1. 互斥锁std::mutex 提供了一种互斥锁的机制,可以确保在任意时刻只有一个线程可以访问被保护的共享资源。
  2. 线程安全:通过加锁和解锁操作,可以确保对共享资源的访问是线程安全的,不会发生数据竞争。
  3. RAII(资源获取即初始化):通常使用 RAII 技术来管理 std::mutex 对象的生命周期,即在作用域内创建 std::mutex 对象并加锁,当作用域结束时自动释放锁。
  4. 锁的类型:除了 std::mutex,C 11 还提供了其他类型的互斥锁,如 std::recursive_mutexstd::timed_mutexstd::recursive_timed_mutex 等,用于满足不同的需求。

下面是一个使用 std::mutex 的基本示例:

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

std::mutex mtx;

void print_hello(int id) {
    mtx.lock(); // 加锁
    std::cout << "Hello, from thread " << id << std::endl;
    mtx.unlock(); // 解锁
}

int main() {
    std::thread t1(print_hello, 1);
    std::thread t2(print_hello, 2);

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

    return 0;
}

在这个示例中,我们定义了一个全局的 std::mutex 对象 mtx,并在 print_hello 函数中使用 mtx.lock()mtx.unlock() 分别对临界区进行加锁和解锁操作,保护了对 std::cout 的访问,避免了多线程并发访问的问题。

unsetunsetstd::lockunsetunset

std::lock 是 C 11 标准库中提供的一个函数模板,用于同时对多个互斥锁进行加锁,以避免发生死锁。std::lock 函数可以同时对多个互斥锁进行加锁,如果无法获得所有锁,则会自动释放已经获得的锁,避免了死锁的发生。

std::lock 的语法形式如下:

代码语言:javascript复制
template <class Lockable1, class Lockable2, class... LockableN>
void lock(Lockable1& lock1, Lockable2& lock2, LockableN&... lockn);

其中 Lockable1Lockable2LockableN 分别表示互斥锁的类型,函数接受任意数量的互斥锁作为参数,并依次对它们进行加锁操作。

下面是一个示例代码,演示了如何使用 std::lock 函数同时对多个互斥锁进行加锁:

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

std::mutex mtx1;
std::mutex mtx2;

void foo() {
    // 同时对多个互斥锁进行加锁
    std::lock(mtx1, mtx2);
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock); // 使用 adopt_lock 参数表示已经获得了锁
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);

    // 在临界区内执行操作
    std::cout << "Critical section in foo" << std::endl;
}

void bar() {
    // 同时对多个互斥锁进行加锁
    std::lock(mtx1, mtx2);
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock); // 使用 adopt_lock 参数表示已经获得了锁
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);

    // 在临界区内执行操作
    std::cout << "Critical section in bar" << std::endl;
}

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

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

    return 0;
}

在这个示例中,我们定义了两个全局的互斥锁 mtx1mtx2,然后在两个线程的函数中使用 std::lock 函数同时对它们进行加锁。由于 std::lock 函数可以同时对多个互斥锁进行加锁,并且会自动释放已经获得的锁,因此可以避免发生死锁。

unsetunsetstd::atomicunsetunset

std::atomic 是 C 11 标准库中引入的用于原子操作的模板类,它提供了一种线程安全的方式来操作共享变量,避免了数据竞争和不一致性问题。std::atomic 类模板支持原子的读-修改-写操作,可以确保操作的完整性和一致性。

下面是 std::atomic 的主要特点和用法:

  1. 原子操作std::atomic 支持一系列的原子操作,包括读取、写入、比较交换、加法、减法、按位与、按位或等操作,这些操作保证了对共享变量的操作的原子性。
  2. 线程安全std::atomic 提供了一种线程安全的方式来访问共享变量,避免了多个线程同时对同一个变量进行操作造成的数据竞争和不一致性问题。
  3. 内存顺序std::atomic 支持指定内存顺序(memory order),通过指定内存顺序可以控制操作的内存可见性和执行顺序,包括 memory_order_relaxedmemory_order_acquirememory_order_releasememory_order_acq_relmemory_order_seq_cst 等。
  4. 原子类型std::atomic 可以用于任意类型的原子操作,包括整数、指针、自定义类型等。

下面是一个简单的示例代码,演示了如何使用 std::atomic 进行原子操作:

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

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 1000000;   i) {
        counter.fetch_add(1, std::memory_order_relaxed); // 原子的加法操作
    }
}

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

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

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

    return 0;
}

在这个示例中,我们定义了一个原子类型的计数器 counter,然后在两个线程中并行地对其进行原子的加法操作,最后输出计数器的最终值。由于原子操作的特性,不会发生数据竞争,最终输出的计数器值是正确的。

unsetunsetstd::call_onceunsetunset

std::call_once 是 C 11 标准库中提供的一个函数,用于确保某个函数只被调用一次,即使在多线程环境下也能保证线程安全。通常情况下,std::call_once 用于在多线程环境下执行初始化工作,以保证全局资源的初始化只进行一次。

std::call_once 的语法如下:

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

其中:

  • flag:是一个标志对象,用于标识函数是否已经被调用过。
  • func:是要调用的函数或可调用对象。
  • args:是传递给函数的参数。

std::call_once 函数会检查 flag 是否已经被设置过,如果没有,则调用 func 函数,并设置 flag 为已调用状态。在多线程环境下,多个线程同时调用 std::call_once,但只有一个线程会执行 func 函数,其他线程会被阻塞直到第一个线程执行完成。

下面是一个示例代码,演示了如何使用 std::call_once 来确保全局资源的初始化只进行一次:

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

std::once_flag flag;
int global_data = 0;

void init_global_data() {
    global_data = 42;
    std::cout << "Global data initialized" << std::endl;
}

void use_global_data() {
    std::call_once(flag, init_global_data); // 只有第一个调用会执行 init_global_data 函数
    std::cout << "Global data used: " << global_data << std::endl;
}

int main() {
    std::thread t1(use_global_data);
    std::thread t2(use_global_data);
    std::thread t3(use_global_data);

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

    return 0;
}

在这个示例中,我们定义了一个全局的 std::once_flag 对象 flag 和一个全局的整型变量 global_data,然后在 use_global_data 函数中使用 std::call_once 来确保 init_global_data 函数只被调用一次。即使多个线程同时调用 use_global_data 函数,但只有一个线程会执行 init_global_data 函数,其他线程会被阻塞,直到第一个线程执行完成。

unsetunsetstd::condition_variableunsetunset

std::condition_variable 是 C 11 标准库中提供的一个条件变量类,用于在多线程编程中实现线程之间的同步。它允许一个或多个线程在某个条件成立时被唤醒,并在条件不满足时等待。通常情况下,std::condition_variable 配合 std::mutex 使用,以实现线程间的等待和通知机制。

下面是 std::condition_variable 的主要特点和用法:

  1. 条件变量std::condition_variable 提供了一种条件变量的机制,用于在条件满足时唤醒等待线程,条件不满足时等待。
  2. 等待和通知:等待线程可以通过 wait() 函数在条件不满足时进入等待状态,而唤醒线程可以通过 notify_one()notify_all() 函数来唤醒等待的线程。
  3. 线程安全std::condition_variable 配合 std::mutex 使用,可以确保线程间的等待和唤醒操作是线程安全的。
  4. 超时等待wait_for()wait_until() 函数允许线程在一定时间内等待条件满足,超时后自动返回。

下面是一个简单的示例代码,演示了如何使用 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_thread() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; }); // 等待 ready 变为 true
    std::cout << "Worker thread is processing" << std::endl;
}

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

    std::this_thread::sleep_for(std::chrono::seconds(2)); // 主线程等待 2 秒

    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true; // 设置 ready 为 true
    }
    cv.notify_one(); // 唤醒等待的线程

    t.join();

    return 0;
}

在这个示例中,我们定义了一个条件变量 cv 和一个布尔变量 ready,线程 worker_thread 在条件变量 cv 上等待,直到 ready 变为 true。主线程等待 2 秒后,设置 ready 为 true,并通过 notify_one() 函数唤醒等待的线程。

需要注意的是,cv.wait() 函数的第一个参数是一个 std::unique_lock<std::mutex> 对象,用于锁定互斥量,确保在等待条件期间其他线程无法修改条件。第二个参数是一个 lambda 表达式,用于检查条件是否满足。

unsetunsetstd::futureunsetunset

std::future 是 C 11 标准库中提供的一个类模板,用于表示异步操作的结果或状态,通常与 std::asyncstd::packaged_taskstd::promise 等异步操作相关的类一起使用。通过 std::future,可以轻松地获取异步操作的结果,并在需要时等待异步操作的完成。

下面是 std::future 的主要特点和用法:

  1. 表示异步操作的结果std::future 用于表示异步操作的结果,可以通过它获取异步操作的结果或状态。
  2. 等待异步操作完成:可以通过 std::future 的成员函数 get() 来等待异步操作的完成,并获取其结果。如果异步操作尚未完成,get() 函数会阻塞当前线程,直到异步操作完成为止。
  3. 检查异步操作状态:可以通过 std::future 的成员函数 valid() 来检查与之关联的异步操作是否有效,以及 wait_for()wait_until() 函数来检查异步操作的状态和等待一段时间。
  4. 异步任务的共享std::future 可以通过 std::shared_future 来实现多个线程共享同一个异步操作的结果。

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

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

int foo() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

int main() {
    // 启动一个异步操作,并获取其 future
    std::future<int> fut = std::async(std::launch::async, foo);

    std::cout << "Waiting for result..." << std::endl;

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

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

    return 0;
}

在这个示例中,我们通过 std::async 启动一个异步操作 foo,然后通过 std::future 对象 fut 获取异步操作的结果。在主线程中调用 fut.get() 等待异步操作完成,并获取其结果,然后打印出结果。

unsetunsetstd::asyncunsetunset

std::async 是 C 11 标准库中提供的一个函数模板,用于创建异步任务并获取与之关联的 std::future 对象,以便在需要时获取异步任务的结果。它是实现异步编程的一种方便方式,能够在多线程环境下执行函数,并且可以方便地获取函数执行的结果。

下面是 std::async 的主要特点和用法:

  1. 创建异步任务std::async 函数用于创建一个异步任务,该任务会在后台线程中执行指定的函数,并返回一个与之关联的 std::future 对象,用于获取异步任务的结果。
  2. 函数执行方式:默认情况下,std::async 函数会以异步的方式执行指定的函数,即函数会在后台线程中执行。但也可以通过 std::launch 参数指定函数的执行方式,包括异步执行 (std::launch::async) 和延迟执行 (std::launch::deferred)。
  3. 获取异步任务结果:通过与 std::future 对象关联,可以在需要时获取异步任务的执行结果。调用 std::future 对象的 get() 方法可以阻塞当前线程,直到异步任务执行完成并返回结果。
  4. 异常处理:如果异步任务中抛出了异常,std::future 对象的 get() 方法会重新抛出异常,从而允许在调用方处理异常。

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

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

int foo() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

int main() {
    // 创建一个异步任务,并获取与之关联的 std::future 对象
    std::future<int> fut = std::async(std::launch::async, foo);

    std::cout << "Waiting for result..." << std::endl;

    // 获取异步任务的结果
    int result = fut.get();

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

    return 0;
}

在这个示例中,我们通过 std::async 函数创建了一个异步任务,指定了函数 foo 作为要执行的任务,并通过 std::launch::async 参数指定了异步执行方式。然后,我们通过调用 fut.get() 方法获取异步任务的结果,该方法会阻塞当前线程,直到异步任务执行完成并返回结果。

0 人点赞