c++11:thread 线程类的使用(详解)

文章目录

  • 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 时间片的争夺


【往期内容】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值