C++11 thread线程的使用
文章目录
- C++11 thread线程的使用
- 构造函数
- 1. `thread() noexcept = default;`
- 2. `thread(thread&) = delete;`
- 3. `thread(const thread&&) = delete;`
- 4. `thread(thread&& __t) noexcept`
- 5. `template<typename _Callable, typename... _Args> explicit thread(_Callable&& __f, _Args&&... __args)`
- 公共成员函数
- 1.`get_id()`
- 2. `join()`
- 3.`detach()`
- 4.`joinable()`
- 5. `std::this_thread::sleep_for()` 和 `std::this_thread::sleep_until()`
- 6. `std::thread::hardware_concurrency()`
- 线程传递参数
在 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;
}
线程传递参数
- 使用函数指针: 可以直接传递函数指针给
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;
}
- 使用 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;
}
- 使用
**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;
}
- 使用
**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;
}
- 使用
**std::async**
:std::async
是一个便利函数,它创建一个新线程来运行给定的函数,并且可以指定返回类型。它在内部使用std::packaged_task
或std::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
(对于常量引用)来明确这一点。