C++11 thread线程的使用

C++11 thread线程的使用

在 C++ 中,线程的使用通常依赖于标准库(C++11 及以后的版本)。C++11 引入了 <thread> 头文件,提供了丰富的线程操作功能,支持多线程编程。线程可以在 C++ 中以并行的方式运行不同的任务,提高程序的效率,尤其在多核 CPU 环境下。

构造函数

thread() noexcept = default;
默认构造函数,创建一个空的线程对象,不会执行任何任务。

thread(thread&) = delete;
thread(const thread&) = delete;
删除了复制构造函数,避免线程对象被复制。

thread(const thread&&) = delete;
删除了常量右值引用构造函数,防止从常量右值引用创建线程。

thread(thread&& __t) noexcept;
移动构造函数,将线程对象的资源从一个对象转移到另一个对象。

template<typename _Callable, typename... _Args>
explicit thread(_Callable&& __f, _Args&&... __args)
主要的线程构造函数,通过可调用对象(如函数、lambda 表达式等)创建线程并启动任务。

1. thread() noexcept = default;

这是 std::thread 的默认构造函数。

  • default 构造函数:这个构造函数是 默认构造函数,它创建一个 无效的线程对象。也就是说,这个线程对象并不会执行任何任务,且它处于“不可连接”状态,不能执行任何线程相关的操作。
  • noexcept:表明这个构造函数 不会抛出任何异常,这是 C++11 的特性,能够帮助编译器进行优化。
std::thread t;  // 创建一个空的线程对象

创建的 t 是一个无效的线程,调用 t.joinable() 返回 false,且不能执行任何操作,除非你将它与一个有效的任务相关联。

2. thread(thread&) = delete;

thread(const thread&) = delete; 这两个构造函数分别是 复制构造函数 和 常量引用复制构造函数,它们被 删除,即无法使用复制构造来创建一个线程对象。

  • 线程对象不能被复制。这是因为线程的操作系统资源和状态不允许简单地复制。线程在执行时持有特定的系统资源(如线程 ID),这些资源不能简单地被复制。
  • 在多线程环境中,线程对象的复制可能导致多个线程试图操作同一个底层操作系统资源,这样做是非常危险的。
std::thread t1;  // 无效的线程
std::thread t2 = t1;  // 编译错误!无法复制线程

为什么删除复制构造函数:

  • 复制构造线程会导致线程的操作系统资源被复制,这在多线程环境中是未定义的行为。删除复制构造函数可以确保线程对象的生命周期管理是明确和安全的。

3. thread(const thread&&) = delete;

这是 常量右值引用构造函数,也是被 删除 的。

  • 常量右值引用构造函数的作用通常是允许从常量右值引用创建对象。然而,这种构造函数对 std::thread 是不必要的,因为它并不符合线程对象的生命周期管理需求。
  • 对于线程对象,编译器删除了这个构造函数,以确保线程对象的资源不会被错误地以常量右值引用传递。
std::thread t1;  // 无效的线程
const std::thread t2 = std::move(t1);  // 编译错误!无法从常量右值引用创建线程

4. thread(thread&& __t) noexcept

这是 移动构造函数。它允许将一个线程对象的资源从一个线程对象转移到另一个线程对象。

  • 通过移动构造函数,线程对象的资源(如线程句柄等)被转移,而不是复制。这种操作避免了多个线程对象共享同一个底层操作系统资源的风险。
  • 移动构造函数通常会将原对象置于一个有效但不可用的状态,通常它的 joinable() 返回 false
std::thread t1(printMessage);  // 创建并启动线程
std::thread t2 = std::move(t1);  // 使用移动构造函数将 t1 的资源转移到 t2

注意:

  • t1 在移动后将不再有效,不能再调用 t1.join()t1.detach()。它的 joinable() 返回 false

5. template<typename _Callable, typename... _Args> explicit thread(_Callable&& __f, _Args&&... __args)

这是 std::thread 的 主构造函数,它允许通过传递一个可调用对象(如函数、lambda 表达式等)以及其参数来创建并启动线程。

  • 这个构造函数是模板构造函数,接收一个可调用对象 __f(如函数、lambda 表达式、函数对象等)和对应的参数 __args,然后创建一个线程并在新线程中执行这个可调用对象。
  • explicit 关键字意味着该构造函数不能进行隐式类型转换。
  • 该构造函数是 C++11 引入的,允许程序员通过线程对象来直接执行任务。
void printMessage(const std::string& msg) {
    std::cout << msg << std::endl;
}

int main() {
    std::thread t(printMessage, "Hello from thread!");  // 通过可调用对象创建并启动线程
    t.join();  // 等待线程结束
    return 0;
}

解释:

  • printMessage 是一个简单的函数,它作为线程的可调用对象传入,"Hello from thread!" 是传递给函数的参数。
  • 线程 t 会在后台执行 printMessage 函数,并打印消息。

公共成员函数

1.get_id()

功能:返回当前线程的唯一标识符。

返回一个 std::thread::id 对象,该对象标识当前线程。如果你有多个线程,可以通过该标识符进行区分。

#include <iostream>
#include <thread>

void printThreadId() {
    std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
}

int main() {
    std::thread t(printThreadId);
    t.join();  // 等待线程完成
    return 0;
}

2. join()

功能:等待线程的执行结束,直到线程执行完毕。

当一个线程对象被创建并启动后,你可以调用 join() 来等待线程完成。如果你不调用 join()detach(),程序可能会导致未定义行为。

3.detach()

功能:将线程从当前的线程对象中分离,允许线程继续独立执行,直到线程完成。

调用 detach() 后,线程会继续执行,但你不再有能力去等待它(例如无法调用 join())。这个函数通常在你希望线程在后台执行时使用,但不需要等待它完成。

#include <iostream>
#include <thread>

void printMessage() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(printMessage);
    t.detach();  // 分离线程,允许它在后台继续运行
    // 程序执行完后,线程仍然在后台执行
    return 0;
}

4.joinable()

功能:检查线程是否可以与当前线程进行连接(即是否已启动且未被分离)。

返回 true 表示线程可以与当前线程 join()detach();如果返回 false,则线程未启动或已被分离。

#include <iostream>
#include <thread>

void printMessage() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(printMessage);
    if (t.joinable()) {
        std::cout << "Thread is joinable." << std::endl;
        t.join();  // 确保线程完成
    }
    return 0;
}

5. std::this_thread::sleep_for()std::this_thread::sleep_until()

功能:这两个函数用于使当前线程暂停执行。

  • sleep_for(duration):使当前线程暂停指定的持续时间。
  • sleep_until(time_point):使当前线程暂停,直到指定的时间点。

这些函数常用于模拟线程的等待或者创建延迟。

#include <iostream>
#include <thread>
#include <chrono>

void printMessage() {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 睡眠2秒
    std::cout << "Hello from thread after 2 seconds!" << std::endl;
}

int main() {
    std::thread t(printMessage);
    t.join();  // 等待线程完成
    return 0;
}

6. std::thread::hardware_concurrency()

功能:返回硬件上的并发线程数,即系统支持的最大并发线程数。

该函数返回一个表示硬件并发能力的提示值。返回值是一个 unsigned 类型的整数。注意:这只是一个提示值,不代表实际可用的线程数。

#include <iostream>
#include <thread>

int main() {
    unsigned int n = std::thread::hardware_concurrency();
    std::cout << "Hardware supports " << n << " concurrent threads." << std::endl;
    return 0;
}

线程传递参数

  1. 使用函数指针: 可以直接传递函数指针给 std::thread 构造函数,如果函数需要参数,可以结合 std::bind 或 C++14 引入的 std::apply 使用。
void worker(int x, double y) {
    // 使用 x 和 y 做一些事情
}

int main() {
    std::thread t(worker, 5, 3.14);
    t.join();
    return 0;
}
  1. 使用 lambda 表达式: 可以创建一个 lambda 表达式并捕获所需的参数,然后传递给 std::thread
int main() {
    int x = 5;
    double y = 3.14;
    std::thread t([&]() { worker(x, y); });
    t.join();
    return 0;
}

或者直接在 lambda 中定义参数:

int main() {
    std::thread t([](int x, double y) { /* 使用 x 和 y 做一些事情 */ }, 5, 3.14);
    t.join();
    return 0;
}
  1. 使用 **std::bind**std::bind 可以用来绑定函数和其参数,生成一个可调用对象,然后传递给 std::thread
#include <functional>

void worker(int x, double y) {
    // 使用 x 和 y 做一些事情
}

int main() {
    std::thread t(std::bind(worker, 5, 3.14));
    t.join();
    return 0;
}
  1. 使用 **std::packaged_task**: 如果需要从线程返回值,可以使用 std::packaged_task 来包装一个函数和其参数,然后通过 std::future 获取返回值。
#include <future>

int worker(int x, double y) {
    // 计算并返回结果
    return x + static_cast<int>(y);
}

int main() {
    std::packaged_task<int(int, double)> task(worker);
    std::future<int> result = task.get_future();
    std::thread(t(std::move(task), 5, 3.14));
    t.join();
    std::cout << "Result: " << result.get() << std::endl;
    return 0;
}
  1. 使用 **std::async**std::async 是一个便利函数,它创建一个新线程来运行给定的函数,并且可以指定返回类型。它在内部使用 std::packaged_taskstd::thread
int main() {
    auto result = std::async(std::launch::async, worker, 5, 3.14);
    std::cout << "Result: " << result.get() << std::endl;
    return 0;
}

请注意,当使用 lambda 表达式或 std::bind 时,如果参数是左值引用,那么它们将在新线程中捕获原始对象的引用,这意味着原始对象在新线程运行期间不能被销毁。如果需要捕获对象的副本,可以使用 std::ref(对于引用)或 std::cref(对于常量引用)来明确这一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liknana

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值