《C++并发编程实战》之线程管理

1.启动线程

        线程启动后是要等待线程结束,还是让其自主运行。当std::thread对象销毁之前还没有做出决定,程序就会终止(std::thread 的析构函数会调用std::terminate())。因此,即便是有异常存在,也需要确保线程能够正确汇入 (joined)或 分离 (detached)。

        如果不等待线程汇入 ,就必须保证线程结束之前,访问数据的有效性。这不是一个新问题——单线程代码中,对象销毁之后再去访问,会产生未定义行为——不过,线程的生命周期增加了这个问题发生的几率。这种情况很可能发生在线程还没结束,函数已经退出的时候,这时线程函数还持有函数局部变量的指针或引用。

 

 2.传递参数

        如代码2.4所示,向可调用对象或函数传递参数很简单,只需要将这些参数作为std::thread构造函数的附加参数即可。需要注意的是,这些参数会拷贝至新线程的内存空间中(同临时变量一样)。即使函数中的参数是引用的形式,拷贝操作也会执行。来看一个例子:

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

        代码创建了一个调用f(3, "hello")的线程。注意,函数f需要一个std::string对象作为第二个参数,但这里使用的是字符串的字面值,也就是char const *类型,线程的上下文完成字面值向std::string的转化。需要特别注意,指向动态变量的指针作为参数的情况,代码如下:

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();
}

        buffer①是一个指针变量,指向局部变量,然后此局部变量通过buffer传递到新线程中②。此时,函数oops可能会在buffer转换成std::string之前结束,从而导致未定义的行为。因为,无法保证隐式转换的操作和std::thread构造函数的拷贝操作的顺序,有可能std::thread的构造函数拷贝的是转换前的变量(buffer指针)。解决方案就是在传递到std::thread构造函数之前,就将字面值转化为std::string
:

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();
}

        相反的情形(期望传递一个非常量引用,但复制了整个对象)倒是不会出现,因为会出现编译错误。比如,尝试使用线程更新引用传递的数据结构:

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
    display_status();
    t.join();
    process_widget_data(data);
}

        虽然update_data_for_widget①的第二个参数期待传入一个引用,但std::thread的构造函数②并不知晓,构造函数无视函数参数类型,盲目地拷贝已提供的变量。不过,内部代码会将拷贝的参数以右值的方式进行传递,这是为了那些只支持移动的类型,而后会尝试以右值为实参调用update_data_for_widget。但因为函数期望的是一个非常量引用作为参数(而非右值),所以会在编译时出错。对于熟悉std::bind的开发者来说,问题的解决办法很简单:可以使用std::ref将参数转换成引用的形式。因此可将线程的调用改为以下形式:

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

这样update_data_for_widget就会收到data的引用,而非data的拷贝副本,这样代码就能顺利的通过编译了。

3.转移所有权

假设通过新线程返回的所有权去调用一个需要后台启动线程的函数,并需要在函数中转移线程的所有权。这些操作都要等待线程结束才能进行,并且需要线程的所有权能够进行转移。

4.确定线程数量

std::thread::hardware_concurrency()在新版C++中非常有用,其会返回并发线程的数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个标识,当无法获取时,函数返回0。

std::accumulate

//对vec中的元素求和,和的初值为0
int sum = accumulate(vec.cbegin(), vec.cend(), 0);

accumulate的第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型。

由于string定义了+运算符,所以我们可以通过调用accumulate来将vector中所以string元素连接起来。

std::string strSum = std::accumulate(str.begin(), str.end(), std::string(""));
 

5.线程标识

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值