文章目录
- 1. 构造函数
- 2. 成员方法
- 2.1 get_id()
- 2.2 join()
- 2.3 detach()
- 2.4 joinable()
- 2.5 operator =
- 3. 静态方法
- 4. 常搭配的方法:this_thread 命名空间
- 4.1 get_id()
- 4.2 sleep_for()
- 4.3 sleep_until()
- 4.4 yield()
- 【往期内容】
c++11 提供了跨平台的线程操作以及线程相关的类,很方便地支持并发编程。我们可以通过 c++11 提供的线程类 std::thread
:创建与管理线程。
下面来介绍 std::thread
类:
头文件:
<thread>
1. 构造函数
-
默认构造函数:
thread() noexcept = default;
此函数创建的是一个
空
线程对象,不代表任何执行线程。 -
利用可调用对象构造:
template<typename _Callable, typename... _Args > explicit thread(_Callable&& __f, _Args&&... __args);
- __f:可调用对象(普通函数、仿函数、类成员函数、匿名函数)
- __args:是要传递给函数 _f 的参数
这是比较常用的构造函数,可以通过其来创建线程,执行函数 _f 中的任务。
【例】
#include <string> #include <thread> void func(int num, std::string str) { // 任务逻辑 } int main() { // 通过 函数 func 创建一个线程对象 std::thread t(func, 1, "string"); }
-
拷贝构造函数:
thread(const thread&) = delete;
可以看到,拷贝构造函数被显式删除了,不允许线程对象之间的拷贝。
-
移动构造函数:
thread(thread&& __t) noexcept
可以将 __t 的线程所有权转移给新的 thread 对象,那么之后 __t 表示一个空线程对象。
2. 成员方法
2.1 get_id()
每个线程都是独一无二的,在 thread 中,使用内部类 id
来唯一标识一个线程。
class id
{
native_handle_type _M_thread;
};
native_handle_type 不同平台是不同类型,但都是 整形。
可以通过 get_id()
函数来获取线程的 id 值:
id get_id() const noexcept
【例】
#include <iostream>
#include <thread>
void func(int num, std::string str)
{
// 任务逻辑
}
int main()
{
std::thread t(func, 1, "string");
std::cout << "子线程 id = " << t.get_id() << std::endl;
return 0;
}
程序启动时默认只有一个线程,这个线程称为
主线程
,其他线程称为子线程
(比如通过线程类创建出的线程)。
那么这里存在两个问题:
-
如果获取主线程的 id:
子线程可以通过线程对象的成员方法get_id()
得到,但是 主线程 并没有对应的线程对象。那么如何获取它的 id 呢?
通过std::this_thread::get_id()
方法:thread::id get_id() noexcept;
此方法可以获取
当前线程
的 id。因此上述程序可以改为:#include <iostream> #include <thread> void func(int num, std::string str) { // 执行此函数的是子线程 t std::cout << "子线程 id = " << std::this_thread::get_id() << std::endl; } int main() { // 默认创建的主线程 std::cout << "主线程 id = " << std::this_thread::get_id() << std::endl; std::thread t(func, 1, "string"); return 0; }
-
程序存在一个 bug:
如上程序直接执行可能只会输出 主线程 id,子线程 id 没有输出
。这是由于 主线程 已经执行完毕,就会将创建出来的所有子线程销毁,并退出程序,此时如果 子线程 还没有执行,因此出现上述情况。
如何解决此问题呢?thread 库提供了两种方法:- 加入式:join()
- 分离式:detach()
2.2 join()
关联另外一个线程,意味着 当前线程在 join() 方法处,只有当所关联的线程终止时,当前线程才继续向下执行,此时子线程被回收——线程阻塞
。
#include <iostream>
#include <thread>
void func(int num, std::string str)
{
std::cout << "子线程 id = " << std::this_thread::get_id() << std::endl;
}
int main()
{
std::thread t(func, 1, "string");
std::cout << "主线程 id = " << std::this_thread::get_id() << std::endl;
t.join(); // ! 当主线程运行到此处时,只有 t 对应的线程终止后,主线程才继续向下执行————主线程被阻塞
return 0;
}
2.3 detach()
进行线程分离,分离主线程与创建的子线程。与 join() 不同,detach() 所分离出的子线程在主线程退出之后,它仍然可以继续运行。在其任务执行完毕后,自动释放自己占用的系统资源。
但这存在一个问题:当 主线程 与 子线程 分离后,那么 主线程将不能对此子线程进行任何控制,即不能使用子线程返回的结果。故此方法适用于 主线程 与 子线程 的任务执行无关联时才使用,一般情况下建议使用
join()
2.4 joinable()
返回主线程与子线程是否处于关联状态。
- true:处于关联状态,则可以调用 join()
- false:不处于关联状态,不可以调用 join()
当出现以下情况,主线程与子线程不处于关联状态:
- 子线程为空
- 子线程被分离了 ( detach() )
- 子线程已经被回收了 ( 主线程调用了 join(),子线程执行完毕后被回收 )
2.5 operator =
thread& operator=(const thread&) = delete;
thread& operator=(thread&& __t) noexcept
可以看到:拷贝赋值运算符被显式删除,只能进行移动赋值(移动线程资源)。
3. 静态方法
- hardware_concurrency
返回硬件线程上下文数量的估计值,如果无法计算该值或该值未正确定义,此方法将返回 0。
在 windows 平台上,返回的是当前计算机的逻辑处理器的数量(CPU核心数 X 每个核心的线程数)
4. 常搭配的方法:this_thread 命名空间
namespace:
std::this_thread
4.1 get_id()
返回当前线程的 id,之前已经介绍过。
4.2 sleep_for()
template <class Rep, class Period>
inline void sleep_for(const chrono::duration<Rep, Period>& Rel_time);
使得当前线程进入 阻塞态,线程将休眠指定时间,代码停止运行,从而让出 CPU 资源。
4.3 sleep_until()
template <class Clock, class Duration>
void sleep_until(const chrono::time_point<Clock, Duration>& Abs_time);
void sleep_until(const xtime *Abs_time);
也是用于线程休眠的,但是 sleep_until 指定是的时间点,即让线程休眠到哪个时刻。
4.4 yield()
于 sleep_for() 不同,此函数使得当前线程转为 就绪态。也就是说,它让当前线程 主动让出 CPU 时间片,但由于是 就绪态,因此仍然参与下一轮的 CPU 时间片的争夺。