《C++并发编程实战》学习(2):管理线程

目录

1. 基本线程管理

1.1 启动线程:

1. 2 线程等待:

detach():  不等待线程任务结束

join(): 等待线程任务结束

2.  线程函数参数传递

3. 线程所有权的转移

4. 运行时可用线程数量

5. 线程标识


1. 基本线程管理

1.1 启动线程:

       当线程thread对象被构造后,开始执行该对象被指定的任务。thread构造方式:

  • 直接指定运行的函数(void func(void)):
//指定运行的函数(void func(void))
void do_some_work();
std::thread my_thread(do_some_work);
  • 带有可调用(callable)类型实例:
//可调用callable类
class backgroud_task
{
public:
    void operator () () const
    {
        do_some_work();
    }
};

background_task f;
std::thread my_thread(f);
  •  可调用类型临时且未命名的变量:
std::thread my_thread(  (backgroud_task()) );
std::thread my_thread{ backgroud_task() };

        

        注意:当直接传递一个临时的且未命名的变量时,编译器会将其解释为函数声明,而非thread对象构造,因此要极其注意上述的括号,额外的括号避免其解释为函数声明。

  • lambda表达式:
std::thread my_thread([]{
                         do_something();
                     });

1. 2 线程等待:

        线程开启后,需显式地决定是否要等待任务完成,如果在thread对象被销毁前未做决定,那么将会调用thread的析构函数terminate()来终止程序。

        将通过以下方式来确定是否等待任务完成:

  • detach():  不等待线程任务结束

void oops ()
{
    int state=0;
    func my_func(state);
    std::thread my_thread(my_func);  //线程执行的任务my_func需要依赖oops作用域内的局部变量
    my_thread.detach();
}

        当oops退出时,my_thread相关联的线程并不受影响,仍在运行;

        my_thread调用detch()会把线程丢在后台运行,也没有直接的方法与之通信,这样分离的线程的所有权和控制权被转交给c++运行时库,以确保与线程相关联的资源在线程退出后能被正确回收。分离的线程通常被称为守护线程。

        注意:当该线程需要访问oops执行期间创建的对象时,因为其已经被销毁,将会出错,因此当决定不等待线程任务结束时,需要使线程函数自包含需要使用的数据(数据copy而不是数据共享);

  • join(): 等待线程任务结束

             当调用thread.join()时,需等该线程结束后,才会继续剩余的code, 而在该线程结束前,程序将会阻塞等待(不占用cpu)。因此,需要谨慎选择调用的join的位置,防止在调用join之前发生了异常而实际没有调用到join, 以确保访问局部状态的线程在函数退出前结束。

        为确保线程在函数退出前完成,可使用以下方式:

try/catch块:

void oops()
{
    int state=0;
    func my_func(state);
    std::thread my_thread(my_func);
    try
    {
        ...

        my_func.join();

        ...        
    }
    catch(...)
    {
        my_func.join();
        throw;
    }
}

注意: 使用try/catch块容易将作用域弄乱,所以并不是一个很合理的方案,更推荐第二种方式;

资源获取即初始化(RAII):

        在其析构函数中进行join();

class thread_guard
{
    std::thread& t;
public:
    //explicit thread_guard (std::thread& t_): t(t_) {}
    explicit thread_guard (std::thread& t_): t(std::move(t_)) {}
    ~thread_guard()
    {
        if (t.joinable())
            t.join();
    }
    /* 构造函数及拷贝赋值运算符被标记为delete,以确保他们不会由编译器自动提供 */
    thread_guard(thread_guard const &)=delete;
    thread_guard& operator=(thread_guard const &)=delete;
}


void oops ()
{
    int state=0;
    func my_func(state);
    //std::thread t(my_func);
    //thread_guard g(t);
    thread_guard g(std::thread(func))
    ...
}

注意:在析构函数中调用join前首先要测试下该thread是不是joinable的,因为一个给定的执行线程join只能被调用一次

2.  线程函数参数传递

        传递参数给可调用对象或函数时,基本上就是将额外的参数传递给std::thread构造函数,参数传递时,参数会被简单粗暴地复制到内部存储空间,在那里新创建的执行线程在访问时,这些被复制进内部空间的参数再转换成期望的参数类型。

        参数被复制到内部存储空间时:

        a. 参数为指针时,直接将指针copy到内部空间,而指针指向的对象并不会被复制,因此,若在执行线程使用该参数前,释放该指针指向的对象,将会出现未定义行为。

void f(int i, std::string const s);
//std::thread t(f, 3, "hello");
std::thread t(f, 3, std::string("hello"));  //避免悬浮指针

        直接使用"hello"传参时,将会以char const*传送,并在内部空间转换为std::string.

        b. 参数为对象或对象的引用时,直接将整个对象copy到内部空间。但是若为引用时,在执行线程要执行时,再将在内部存储空间的副本对象的引用传递给执行线程,是无法实现引用的效果的。

        可用std::ref来包装需要引用的参数,用于显性指示对象的引用,如:

void update_data_for_widget(widget_id w, widget_data & data);
std::thread t(update_data_for_widget, w, std::ref(data));

       c. 线程执行特定对象的成员函数,如, 

class X
{
public:
    void do_lengthy_work();
};

X my_x;

//线程调用的是my_x.do_lengthy_work()
std::thread t(&X::do_lengthy_work, &my_x);

        d. 当传入的参数不能被复制只能被移动时,可以通过std::move来将源对象的所有权先被转移到新创建的线程的内部存储中,然后,再在执行的时候传入。

3. 线程所有权的转移

        std::thread的实例是可移动的,但不是可复制的。线程的所有权的移动也是通过std::move完成的。当thread对象A将所有权转交给一个已经有关联的线程B时,线程B会调用terminate()来终止程序。

        可以利用哪些移动感知的容器来生成一批线程,然后等待他们完成,如,std::vector<std::thread>, 将线程放入vector中是线程迈向自动管理的第一步。

4. 运行时可用线程数量

        std::thread::hardware_currency() 返回一个对于给定程序执行时能够真正并发运行的线程数量的指示,注意,返回的线程数已包含当前线程,所以,在构建最大线程数时,需要减一。若该信息不可用,则返回为0.

5. 线程标识

        线程标识符为std::thread::id类型,线程库为该类型的对象提供了完整的比较运算符,及排序方法。因此,thread::id可以在关系型容器中被用作主键或被排序。

        std::thread::id可通过直接调用thread对象的get_id()来获取,或者通过调用std::this_thread::get_id()来获取。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值