「C++ 多线程」std::thread scoped_thread 实现
文章大图来源:pixiv_id=104139863
scoped_thread 定义
scoped_thread 是一种资源管理类,用于确保线程在适当的时候被正确地加入(join)或分离(detach),更好地管理线程资源。scoped_thread 利用了 RAII(Resource Acquisition Is Initialization)机制,也可以称为 资源获取即初始化。
scoped_thread 实现及作用
std::thread 成员
通常我们在 scoped_thread 类中定义一个私有 std::thread 对象成员变量,用于存储实际的线程对象。scoped_thread 类可以对这个 std::thread 线程进行管理,控制其生命周期等操作。
构造函数的编写
-
explicit 关键字
使用 explicit 关键字修饰构造函数,是为了防止隐式转换。这意味着该构造函数只能通过显式地传入 std::thread 对象来进行调用,而不可以通过一些其他类型隐式地转换为 std::thread 对象传入构造函数。
-
std::move 移动语义
当 scoped_thread 对象被创建时,获取一个 std::thread 线程对象
t_
,并且通过 std::move 移动语义将这个 std::thread 对象的线程t_
归属权转移到 scoped_thread 对象的成员变量t
中。采用移动语义,也更加符合线程的资源管理方式,符合线程具有独占性所有权的特点,高效实现线程归属权的转移,能够避免不必要的拷贝操作。
-
线程是否有效
在构造函数体中,通过
if(!t.joinable()){...}
语句来检查接收到的线程对象是否关联了函数且有效(joinable)。有可能传入的 std::thread 对象是一个空线程(没有关联任何线程接口函数),或者已经
join()
或detach()
过了。如果出现这些情况,需要抛出异常。
析构函数的编写
在析构函数中,首先通过 if(t.joinable()){...}
再次检查 std::thread 成员变量 t
所代表的线程对象是否是有效可连接的。如果已经 join()
或 detach()
过了,就不需要再进行后续的操作了。
如果线程 t
确定是 joinable 的,那么调用 join()
函数。这使得 scoped_thread 对象的线程 t
在主线程 main
执行结束时调用析构函数,进而自动调用 join()
,让主线程等待 scoped_thread 对象的线程 t
(也即子线程)直到其执行完成。
通过在析构函数中执行 join()
操作,利用了 RAII 机制,保证了无论在什么情况下,线程都能被正确地结束,避免了线程被意外中断等结果。
禁用拷贝与拷贝赋值
众所周知,std::thread 是不可以拷贝和拷贝赋值的,因此我们定义的 scoped_thread 类也需要禁用拷贝构造函数和拷贝赋值函数(声明为 = delete
),避免出现多个 scoped_thread 对象同时管理一个线程的混乱情况(一个 std::thread 同一时刻只能拥有一个线程的归属权)。
scoped_thread 简单代码实现
1 |
|
运行结果如下:
可以看出,scoped_thread 对象的子线程正常执行完毕。
当主线程结束时,我们定义的 scoped_thread 对象 st 也超出了作用域,此时会自动调用析构函数。在我们实现的 scoped_thread 类的析构函数中,会自动执行 join()
操作,等待关联的线程(关联函数 foo()
)执行完成,确保线程资源被正确处理。之后 main
函数才会真正结束,整个程序正常退出。