roscpp_overview詳細解讀
roscpp_overview詳細解讀
參考
前言
爲何須要多線程?
學習記錄
Callbacks and Spinning
ROS內部的線程模型
單線程Spinning
多線程Spinning
高級:使用多個回調函數隊列
總結
原理總結
多線程使用
程序示例
帖子答案
The theory
Your case
Conclusion
Proposed solution
TOC
參考
前言
爲何須要多線程?
學習記錄
Callbacks and Spinning
ROS內部的線程模型
單線程Spinning
多線程Spinning
高級:使用多個回調函數隊列
總結
原理總結
多線程使用
程序示例
帖子答案
The theory
Your case
Conclusion
Proposed solution
參考
前言
- 不少模塊,比較基礎的模塊須要深刻理解
- 有一些關於多線程編程的知識須要知道
- 主要學習一下模塊的內部機理
- Callbacks and Spinning
- Publishers and Subscribers
- Timers
- NodeHandles
- Logging
- Time
- CppStyleGuide
爲何須要多線程?
- 好比說,ROS須要接受相機傳來的點雲,而且檢測平面,獲得法向量,顯示在RViz上,這樣須要訂閱sensormsgs::point2消息而且註冊點雲處理函數foo(),因爲點雲處理函數須要較長時間執行,因此若是沒有多線程(是單線程spin),則在接收到一個點雲消息數據後,訂閱函數會阻塞,會一直執行處理函數,不會接收到下一個點雲消息,此時若是沒有定義消息接收隊列,則點雲數據會被丟棄,在RViz顯示上也會很卡頓。
學習記錄
Callbacks and Spinning
ROS內部的線程模型
- roscpp並不把線程模型暴露給程序員,這意味着,在代碼背後,程序其實有不少線程在運行,好比網絡管理,任務調度等。
- 可是roscpp卻容許咱們使用多線程來調用回調函數,這又意味着你必須寫一句ros::spin()來使得其能夠被調用
They will have an effect on the subscription queue, since how fast you process your callbacks and how quickly messages are arriving determines whether or not messages will be dropped.node
單線程Spinning
ros::init(argc, argv, "my_node"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe(...); ... ros::spin();//若是不Ctrl+C或者出現異常,這個循環不會結束 ros::Rate r(10); // 10 hz while (should_continue) { ... do some work, publish some messages, etc. ... ros::spinOnce(); r.sleep(); }//這個循環依然不會結束不是麼 #include <ros/callback_queue.h> ros::NodeHandle n; while (ros::ok()) { ros::getGlobalCallbackQueue()->callAvailable(ros::WallDuration(0.1));//從一個全局隊列中取出 } #include <ros/callback_queue.h> ros::getGlobalCallbackQueue()->callAvailable(ros::WallDuration(0));
多線程Spinning
- 從多個線程來調用回調函數
- 分阻塞型(同步)和非阻塞型(異步),主要表現爲是否阻塞調用它的主調線程
ros::MultiThreadedSpinner spinner(4); // Use 4 threads spinner.spin(); // spin() will not return until the node has been shutdown //or ros::AsyncSpinner spinner(4); // Use 4 threads spinner.start(); ros::waitForShutdown();
高級:使用多個回調函數隊列
- 默認狀況下,回調函數都在一個全局隊列裏面,在特殊狀況下,能夠自定義多個回調函數隊列以提升性能。
- 適用範圍
- 長時間運行的服務
- 運行特別費時的回調函數
總結
原理總結
- spin()的目的是啓動一個新的線程去獲取隊列中的回調函數並調用
- 分單線程,同步多線程和異步多線程,這些都有內置的語句
- 還能夠自定義回調函數隊列
多線程使用
- 很是耗時的運算建議放在主線程中
while(ros::ok()) { leg_detector.process(latest_scan); ros::spinOnce(); }
- 回調函數建議放簡單函數,最多放數據複製函數
程序示例
- 建議查看 wall_extrator_pcl,cpp by Willow Garage Inc.
- 該程序接收點雲數據和一個檢測牆體的服務,有兩個回調函數cloudCB()和wall_detect(),cloudCB()簡單地將點雲消息指針傳遞給類的私有常量指針,而服務回調函數根據須要執行檢測牆體功能,向RViz發送Marker消息標記。
- 該程序在主線程中使用了同步2個線程spinner,兩個線程分別執行兩個回調函數(表面上看是這樣沒錯,但實際上還得看操做系統調度)
- 兩個線程間經過條件變量結合互斥量來實現線程同步
void cloudCb(const boost::shared_ptr<const sensor_msgs::PointCloud2>& cloud) { boost::mutex::scoped_lock lock(cloud_mutex_); cloud_msg_ = cloud; cloud_condition_.notify_all(); } bool detect_wall (stereo_wall_detection::DetectWall::Request &req, stereo_wall_detection::DetectWall::Response &resp) { ros::Time start_time = ros::Time::now(); // wait for new pointcloud boost::mutex::scoped_lock lock(cloud_mutex_); cloud_condition_.wait(lock); //...... } int main (int argc, char** argv) { ros::init (argc, argv, "wall_extractor"); PlanarFit p; ros::MultiThreadedSpinner spinner(2); // extra thread so we can receive cloud messages while in the service call spinner.spin(); return (0); }
帖子答案
- Briefly: It won't work as you expect and you probably do not need such a complex way of designing your node.
The theory
- When it comes to communication in roscpp, two kind of objects are handling callbacks:
callback queuesspinnersA spinner is an object that has the ability to call the callbacks contained in a callback queue. A callback queue is the object in which each subscriber will add an element each time a message is received by resolving which kind of message should call which callbacks (and with which arguments). - Regarding the spinners, there is currently three implementations available in roscpp:
Single thread spinner: the default one, takes the messages contained in a callback queue and process the callbacks one by one while blocking the execution of the thread that called it.Multi-threaded spinner: spawns a couple of threads (configurable) that will execute callbacks in parallel when messages are received but blocks the execution of the thread that called it.Asynchronous spinner: spawns a couple of threads (configurable) that will execute callbacks in parallel while not blocking the thread that called it. The start/stop method allows to control when the callbacks start being processed and when it should stop.These objects may be instantiated manually in advanced ROS nodes but to avoid verbose initialization, analternative, object-less, API is provided through functions in the ROS namespace. Aka ros::spin(),ros::spinOnce() and so on. This API rely on a default callback queue implemented as a singleton which is accessible through the ros::getGlobalCallbackQueue() function. - So basically when you call the ros::spinOnce() function, a single-thread spinner is created and its spin method is called once using the default callback queue (see init.cpp from roscpp).
- And to finish, when you create a Subscriber, you pass it a NodeHandle and each NodeHandle has an associated callbackqueue that default to the global one but which can be overridden using thegetCallbackQueue/setCallbackQueue methods.
Your case
- If you take a look at spinner.cpp you will see there is a mutex that make the SingleThreader thread-safe whilediscouraging you to use it in this case (line 48).
Conclusion
- what you do is safe but will trigger regularly ROS error messages as there is chances that several instances of ros::SpinOnce will be executed in parallel.
Proposed solution
- Considering your applications, I think your callbacks are just not written as they should. A callback should stay as simple and fast as possible. In most of the cases, a callback is just feeding ROS data to your own classes which is usually as simple as copying data (and maybe converting them). It will help you ensuring that your callbacks are thread-safe (if you want to convert your node to a nodelet for instance, one day) and avoid making "ros::Spin" blocking for a long time, even in the case you are using the single-threaded spinner.
- Typically, if you want to do time-consuming computations such as "leg detection", the callbacks are definitively not the place to do it.
- Just, copy your data to a LegDetector object instance and call in your main thread the method that will do the heavy work. In particular here, you really don't care about losing old messages if your node is not fast enough so there is really no reason to pay the extra-burden of all the multi-thread infrastructure. Use Boost.Bind for instance, to pass a reference to your LegDetector class to the callback that will just copy the relevant laser data into it.
- If, at some point, you need to synchronise the received data, use the message_filters API. In this case again, trying to use multi-thread to do so is definitively a bad idea.