2.线程管理

启动线程

  • 使用C++线程库启动线程,可以归结为构造std::thread对象。

void do_some_work();

std::thread my_thread(do_some_work);

  • std::thread可以用可调用类型构造,将带有函数调用符类型的实例传入std::thread类中,替换默认的构造函数。

class background_task{
public:
    void operator()() const{
        do_something();
        do_something_else();
    }
};

background_task f;
std::thread my_thread(f);
  • 提供的函数对象会复制到新线程的存储空间当中,函数对象的执行和调用都在线程的内存空间中进行。函数对象的副本应与原始函数对象保持一致,否则得到的结果会与我们的期望不同。
  • 如果你传递了一个临时变量,而不是一个命名的变量;C++编译器会将其解析为函数声明,而不是类型对象的定义。

std::thread my_thread(backgroung_task());

  • 使用在前面命名函数对象的方式,或使用多组括号,或使用新统一的初始化语法,可以避免这个问题

std::thread my_thread( (backgroung_task()) );

std::thread my_thread{ backgroung_task() };

  • 使用lambda表达式也能避免这个问题。允许使用一个可以捕获局部变量的局部函数

std::thread my_thread([]{

        do_something();

        do_something_else();

});

  • 启动线程之后:等待线程结束(加入式)   /   让其自主运行(分离式)

必须在std::thread对象销毁之前做决定,否则你的程序将会终止(std::thread的析构函数会调用std::terminate() )

  • 如果不等待线程,就必须保证线程结束之前,可访问的数据得有效性

这种情况很可能发生在线程还没结束,函数已经退出的时候,这时线程函数还持有函数局部变量的指针或引用。

处理方法:将数据复制到线程中,而非复制到共享数据中,对于对象中包含的指针和引用还需谨慎

  • 只能对一个线程使用一次join(),一旦使用过join(), std::thread 对象就不能再次汇入了。当对其使用joinable()时,将返回false。
  • 避免应用被抛出的异常所终止。通常,在无异常的情况下使用join()时,需要在异常处理过程中调用join(),从而避免生命周期的问题。
  • 调用 std::thread 成员函数detach()来分离一个线程。之后,相应的 std::thread 对象就与实际执行的线程无关了,并且这个线程也无法汇入。
  • 当 std::thread 对象使用t.joinable()返回的是true,就可以使用t.detach()。
  • 不仅可以向 std::thread 构造函数传递函数名,还可以传递函数所需的参数(实参)。当然,也有其他方法可以完成这项功能,比如:使用带有数据的成员函数,代替需要传参的普通函数。
struct func;
void f()
{
    int some_local_state = 0;
    func my_func(some_local_state);
    std::thread t(my_func);
    try{
        do_something_in_current_thread();
    }catch(...){
        t.join();
        throw();
    }
    t.join();
}

向线程函数传递参数

简单的传递

简单的传递,就在函数名称后面进行填写。

void f(int i;std::string const& s);
std::thread t(f,3,"hello");

 默认的传值方式是值拷贝,thread会用新的地址来接收变量的值,即使函数接受的参数是引用也无效。而且,指向动态变量的指针作为参数传递时,指针指向本地变量,可能在转化的时候崩溃。

void f(int i,std::string const& s);
void oops(int some_param)
{
    char buffer[1024];    //1
    sprintf(buffer,"%i","some_param");
    std::thread t(f,3,buffer);    //2
    t.detach();
}

void f(int i,std::string const& s);
void not_oops(int some_param){
    char buffer[1024];
    sprintf(buffer,"%i",some_param);
    std::thread t(f,3,std::string(buffer));//使用std::string避免悬垂指针
    t.detach();
}

 这种情况下,buffer2是一个指针变量,指向本地变量,然后本地变量通过buffer传递到新线
程中2。并且,函数有很有可能会在字面值转化成std::string对象之前崩溃(oops),从而导致一些未定义的行为。并且想要依赖隐式转换将字面值转换为函数期待的std::string对象,但因std::thread的构造函数会复制提供的变量,就只复制了没有转换成期望类型的字符串字面值。

解决方案就是在传递到std::thread构造函数之前就将字面值转化为std::string对象:

还可能遇到相反的情况:期望传递一个引用,但整个对象被复制了。当线程更新一个引用传
递的数据结构时,这种情况就可能发生,比如:

//期望传递一个引用,但整个对象被复制了
void update_data_for_widget(widget_id w,widget_data& data);    //1
void oops_again(widget_id w){
    widget_data data;
    std::thread t(update_data_for_widget,w,data);    //2
    //改为
    //std::thread t(update_data_for_widget,w,std::ref(data));

    display_status();
    t.join();
    process_widget_data(data);    //3
}

虽然update_data_for_widget1的第二个参数期待传入一个引用,但是std::thread的构造函数2并不知晓;构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量。当线程调用update_data_for_widget函数时,传递给函数的参数是data变量内部拷贝的引用,而非数据本身的引用。因此,当线程结束时,内部拷贝数据将会在数据更新阶段被销毁,且process_widget_data将会接收到没有修改的data变量3

如果需要传递,需要使用std::ref()函数

std::thread t(update_data_for_widget,w,std::ref(data));

在这之后,update_data_for_widget就会接收到一个data变量的引用,而非一个data变量拷贝的引用。

使用类的成员函数作为线程函数

class X{
public:
    void do_lengthy_work();
}
X my_x;
std::thread t(&X::do_lengthy_work(),&my_x); //1

class X{
public:
    void do_lengthy_work();
};
X my_x;
int num(0);
std::thread t(&X::do_lengthy_work,&my_x.num);

这段代码中,新线程将my_x.do_length_work作为线程函数;my_x的地址作为指向对象提供给函数,也可以为成员函数提供参数,std::thread第三个参数就是成员函数的第一个参数,以此类推。

但是提供的成员函数的参数可以移动,但不能拷贝。当原对象是一个临时对象,自动进行移动操作,但当原对象是命名变量,那么转移的时候就需要使用std::move进行显示移动。下面展示的std::move如何转移一个动态对象到一个线程中去:

void process_big_object(std::unique_ptr<big_object>);
std::unique_ptr<big_object> p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object,std::move(p));

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值