std::thread的使用

本文详细记录了将顺序执行的USB摄像头图像采集代码转换为多线程的过程,遇到的问题及解决方案。在尝试创建线程时,错误提示禁止使用不合格或括号内的非静态成员函数地址。通过添加类名前缀解决了这个问题,但随后出现了std::thread参数在转换为右值后不可调用的错误。经过研究发现,需要使用std::ref()来正确传递对象引用。最终成功实现了多线程的代码,并对并发编程的困难进行了反思。
摘要由CSDN通过智能技术生成

顺序执行的代码转换成多线程执行;

艰难地进行了几次变迁, 整理问题如下:
原本代码如下:

if (left_cam_.is_capturing()) {
        if (!take_and_send_image(left_cam_,left_info)) ROS_WARN("USB camera did not respond in time.");
}
if(right_cam_.is_capturing()){
        if(!take_and_send_image(right_cam_, right_info)) ROS_WARN("USB camera did not respond in time.");
}

想要改成 多线程的形式:

if (left_cam_.is_capturing() &&right_cam_.is_capturing()) {
    std::thread takeLeftThread(&take_and_send_image,left_cam_,left_info);
    std::thread takeRightThread(&take_and_send_image,right_cam_,right_info);
    takeLeftThread.join();
    takeRightThread.join();
    std::cout << "Finished Capture Left and Right " << endl;
}

报错如下:

 error: ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function.  Say '&usb_cam::UsbCamNode::take_and_send_image' [-fpermissive]
|   344 |         std::thread takeLeftThread(&take_and_send_image,left_cam_,left_info);

感觉这个问题很复杂的样子, stackoverflow上大家也都在各种讨论, SO上的讨论 错误信息翻译过来就是:

ISO C ++禁止使用不合格或带括号的非静态成员函数的地址形成指向成员函数的指针

我看了一下大家的分析, 简单地翻译过来如下:

作者遇到的问题是代码在下面这行时报错如上:

fPtr = &(myfoo::foo); 

高分解答是希望作者把这行代码修改为如下:

fPtr = &myfoo::foo;

详细的解释里, 其中有一句很重要的话:

A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses [...]

第一次觉得英语太难了... 翻译过来大概是 指向成员函数的指针只有在 有显示引用&以及该函数没有被括号括起来的情况下 才能形成,stackoverflow上的楼主遇到的就是这个问题,大家讨论的也更深一点, 也就是说为什么会有这个设定,以及这个设定的好处; (想要了解的可以看网页上第二个回答,这里不再赘述,主要也不保证我能理解对[捂脸哭]);

虽然和我的实际情况,不是特别一样,其实效果是差不多的, 我的是把非静态成员函数直接用于新建线程,这样无法形成一个有效的pointer, 因此添加上类名之后,错误消失;

改动后的代码如下:

if (left_cam_.is_capturing() &&right_cam_.is_capturing()) {
    std::thread takeLeftThread(&Usb_cam_node::take_and_send_image,left_cam_,left_info);
    std::thread takeRightThread(&Usb_cam_node::take_and_send_image,right_cam_,right_info);
    takeLeftThread.join();
    takeRightThread.join();
    std::cout << "Finished Capture Left and Right " << endl;
}

修改后的代码又遇到了下面这个问题:

In instantiation of 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = bool (usb_cam::UsbCamNode::*)(usb_cam::UsbCam&, usb_cam::UsbCamNode::camera_node_info&); _Args = {usb_cam::UsbCam&, usb_cam::UsbCamNode::camera_node_info&}; <template-parameter-1-3> = void]':
|nodes/usb_cam_node.cpp:344:88:   required from here
| recipe-sysroot/usr/include/c++/9.1.0/thread:120:44: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues
|   120 |           typename decay<_Args>::type...>::value,
|       |                                            ^~~~~

问题的主要提示是这句话, 即std::thread 的参数在转换成rvalues后变得不可调用

static assertion failed: std::thread arguments must be invocable after conversion to rvalues

这里说一下我的函数定义:

bool take_and_send_image(UsbCam &cam, camera_node_info &camera_info)
{
  //DO something
  return 0;
}

上面定义中的形参分别是类UsbCam和结构体camera_node_info; 

看了一下网上的帖子,总结一下, 大概有两种写法会触发上面的错误:

触发invocable的第一种错误类型 

第一种是在使用非静态成员函数创建线程的时候,没有添加类的实例化对象进去, 也就是我上面的代码中如果不添加 this, 则会报此错误;

大神们使用了std::invoke()来举例:

If you're using invoke on non-static member, you have to pass instance of Class, otherwise you will take another overload which won't identify first argument as invokable.

当用非静态函数创建invoke时, 需要将其对应的实例化对象一起传入, 否则,函数将会重新进行重载,导致传参变成invokable,即不可以调用;

错误代码为:

 foo f;
 std::thread t5(&foo::bar);//这里会报错

正确的代码如下:

 foo f;
 std::thread t5(&foo::bar, &f);//这里的f必须传入进来

第二种会触发上面这个错误的情况是下面的情况,传入参数的问题;

为了解决刚才的问题,我将代码改成了下面的形式, 给每个传入的参数添加了 引用符号, 但是依然报错;

std::thread takeLeftThread(&UsbCamNode::take_and_send_image,this, &left_cam_,&left_info);
std::thread takeRightThread(&UsbCamNode::take_and_send_image,this, &right_cam_,&right_info);

 按照大神们的解答, 这种以引用符号的形式并不能将该对象进行传递, 因此需要借助于std::ref();

std::thread takeLeftThread(&UsbCamNode::take_and_send_image,this, std::ref(left_cam_),std::ref(left_info));
std::thread takeRightThread(&UsbCamNode::take_and_send_image,this, std::ref(right_cam_),std::ref(right_info));

将代码修改成上面的部分后,即可编译通过;具体的解释可以浓缩成一句话:

std::ref使用于那些只能使用值传递而不能使用引用传递的地方;

有一点一知半解,更详细的可以参考这里
到此,问题算是解决了,莫名感觉到一阵空虚.....

并发编程这条路太艰辛了,我上面的总结如果有问题,还麻烦大神们不吝赐教,一定指出来;谢谢!!

REF:

禁止获取不合格或括号内的非静态成员函数的地址

C++官网thread解释

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值