Linux开发中的线程管理(C++11 std::thread)
前言
std::thread 是 C++11 引入的一个类,是 C++11 标准库中的一个关键特性,它提供了一种在 C++ 程序中创建和管理线程的方法。通过使用 std::thread,可以很容易地在你的 C++ 程序中创建多线程应用程序。每个 std::thread 对象都代表了一个独立的执行线程,这些线程可以并行地执行不同的任务。
1. std::thread线程的创建
std::thread();//默认构造函数template< class Function, class... Args >
explicit thread(Function&& f, Args&&... args);//可调用对象构造thread(thread&& other) noexcept;//移动构造函数thread(const thread&) = delete;//拷贝构造(删除)
std::thread是不可拷贝的,但可以移动。
该构造函数接收另一个std::thread对象,并接管其线程。
std::thread对象不能被拷贝,因为线程的所有权是唯一的。
这样设计是为了避免多个std::thread对象管理同一个线程,导致未定义行为。
#include <iostream>
#include <thread>void func(int x) {std::cout << "Thread function with arg: " << x << std::endl;
}int main() {std::thread thread; // 默认构造,不与任何线程关联std::cout << "t is joinable? " << t.joinable() << std::endl; // 输出 0(false)std::thread thread_1(func, 10); // 创建线程并执行 func(10)thread_1.join(); // 等待线程执行完毕std::thread thread_2(func);std::thread thread_3 = std::move(thread_2); // t1移动到t2,t1不再管理线程thread_3.join();std::thread thread_4(func);// std::thread t2 = t1; // ❌ 错误,不能拷贝return 0;
}
一般创建子线程的流程
{// 创建client端的socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);// 连接服务器成功,启动接收子线程std::thread readTask(readTaskHandler, clientfd); // pthread_createreadTask.detach(); // pthread_detach
}
2. std::thread线程的管理
等待线程结束
主线程可以通过调用join()
方法来等待其他线程完成。当线程完成后则会自动释放申请的资源,进行收尸操作。
如果不调用join()
或 detach()
,则当 std::thread 对象被销毁时,程序会调用 std::terminate()
终止未结束的线程,抛出一个异常。这可能会导致资源泄露或其他问题。
分离线程
默认情况下,新创建的线程会与创建它的线程(通常是主线程)同步。可以通过调用 detach()
方法来分离线程,这样主线程就可以继续执行,而不需要等待新线程结束。调用 .detach()
会使当前线程独立运行,从而使得该线程在后台运行,而不阻塞主线程或其他线程的执行。具体来说.detach()
的作用是将线程从其所属的 std::thread 对象中分离出来,这样 std::thread 对象就不再管理这个线程的生命周期了。
通过调用detach()
方法,可以使线程独立于主线程运行。一旦线程被分离,就不能再次对其调用 join()
或 detach()
,并且当主线程结束时,分离的线程将继续运行,直到完成其任务。
#include <iostream>
#include <thread> void threadFunction() { for (int i = 0; i < 5; ++i) { std::cout << "Thread is running, count: " << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作 }
} int main() { std::thread thread_1(threadFunction); // 创建并启动线程 // 在这里可以做一些其他工作,但这里我们只是等待线程完成 thread_1.join(); // 等待线程t完成 std::cout << "Thread has finished execution." << std::endl; std::thread thread_2(threadFunction); thread_2.detach(); // 分离线程,主线程继续执行 // 注意:分离后,主线程结束时不会等待这个线程完成 // 这里的程序可能在后台线程完成之前就结束了 // 在实际应用中,你可能需要其他机制来确保所有线程都已完成return 0;
}
std::thread的不安全操作
#include <iostream>
#include <thread> void threadFunction() { // 假设这里有一些耗时的操作
} int main() { { std::thread t(threadFunction); // 创建一个线程,但作用域仅限于这个块 // 注意:我们没有调用 t.join() 或 t.detach() // 当这个块结束时,t 的析构函数将被调用,但线程仍在运行 // 这将导致 std::terminate() 被调用,从而终止程序 } // 这段代码不会被执行,因为程序已经在上面的块结束时终止了 std::cout << "This line will not be executed." << std::endl; return 0; // 永远不会到达这里
}
上述情况应该使用detach()分离出来,否则离开作用域后创建的子线程将直接进行析构
如果不调用 join()
或 detach()
,并且 std::thread 对象在作用域结束时被销毁,程序将调用 std::terminate()
终止。这是因为 C++ 标准要求,如果 std::thread 的析构函数被调用,并且线程仍然是可连接的(即,没有被 join()
或 detach()
),则必须调用 std::terminate()
。
为了避免这种情况,应该在 std::thread 对象被销毁之前调用 join()
或 detach()
。如果知道线程何时会结束,通常使用 join()
是更安全的选择,因为它可以确保主线程等待子线程完成。如果您不关心子线程的完成时间,或者想要让子线程独立于主线程运行,那么可以使用 detach()
。但是,请注意,使用 detach()
时需要小心管理线程的生命周期和资源共享。
3. std::thread 的常用成员函数
成员函数 | 功能概述 | 注意/用途 |
---|---|---|
joinable() | 检查线程对象是否可被 join。如果线程正在执行或可执行且未被 join 或 detach,则返回 true;否则返回 false。 | 不带参构造的 std::thread 对象或已被移动的 std::thread 对象不可 join。 |
join() | 阻塞当前线程,直到被 join 的线程完成其执行。如果线程未启动或已被 join/detach,则行为未定义(通常抛出异常)。 | 确保子线程在主线程继续执行之前完成其任务。 |
detach() | 将线程与 std::thread 对象分离,允许线程在后台继续运行。分离后,线程不再与任何 std::thread 对象关联,且不能被 join。 | 一旦线程被 detach,就无法再通过 std::thread 对象控制或等待该线程。 |
get_id() | 获取线程的标识符(ID),类型为 std::thread::id。该 ID 在线程生命周期内唯一,但结束后可能会被重用。 | 用于标识和区分不同的线程。 |
native_handle() | 获取与实现相关的本机线程句柄。具体行为和返回值取决于操作系统和 C++ 运行时库。 | 使用时需了解当前平台的线程 API,并谨慎处理句柄以避免资源泄露或安全问题。 |
hardware_concurrency() | 返回硬件支持的并发线程数量的估计值(静态成员函数)。此值是一个提示,表示系统可能支持的并行线程数,但并非绝对限制。 | 帮助开发者在创建线程时决定合适的线程数量。 |
swap() | 交换两个 std::thread 对象的状态。如果两个对象都表示活动的线程,则它们的执行不受影响;但如果一个对象是空的,则另一个对象将不再与任何线程关联。 | 在需要转移线程所有权或管理线程集合时很有用。 |