ROS學習之 cpp回撥函式和輪轉(spin)
阿新 • • 發佈:2019-01-26
wiki連結: http://wiki.ros.org/roscpp/Overview/Callbacks%20and%20Spinning
資料(雲飛機器人實驗室的一篇小文): http://www.yfworld.com/?p=2318
這篇文章會幫助理解ros::spin()呼叫和回撥函式的關係,其參考連結:http://answers.ros.org/question/11887/significance-of-rosspinonce/
關於spin一詞: spin在英語中是"紡紗,吐司,旋轉,延伸"等意思,在看這部分時,腦子裡想的是怎麼翻譯這個詞,
看了上面的這篇資料,決定將它翻成"輪轉"的意思.訊息會被壓入訊息佇列,而訊息回撥函式也回被壓入佇列,訊息到來並不會立即執行訊息處理回撥函式,而是在呼叫ros::spin()之後,才進行訊息處理的輪轉,訊息回撥函式統一處理訂閱話題的訊息.
好了,開始正題.
roscpp不會在你的應用中明確一個執行緒模型.也就是說即使roscpp會在幕後使用多執行緒管理網路連結,排程等,但它不會將自己的執行緒暴露在你的應用中.
roscpp允許你的回撥函式被任意多執行緒呼叫,如果你願意.
最後的結果可能是你的回撥函式將沒有機會被呼叫.最常用的方法是使用ros::spin()呼叫.
注意:回撥函式的排隊和輪轉,不會對內部的網路通訊造成影響.它們僅僅會影響到使用者的回撥函式何時發生.它們會影響到訂閱者佇列.因為處理你回撥函式的速度,你訊息到來的速度,將會決定以前的訊息會不會被丟棄.
1.單執行緒下的輪轉
最簡單的單執行緒spin的例子就是ros::spin()自己.
ros::init(argc, argv, "my_node"); //初始化節點
ros::NodeHandle nh; //建立節點控制代碼
ros::Subscriber sub = nh.subscribe(...); //建立訊息訂閱者
...
ros::spin(); //呼叫spin(),統一處理訊息
在這裡,所有的使用者回撥函式將在spin()呼叫之後被呼叫.
ros::spin()不會返回,直到節點被關閉,或者呼叫ros::shutdown(),或者按下ctrl+C
另一個常用的模式是週期性地呼叫ros::spinOnce():
ros::Rate r(10); // 10 hz
while (should_continue)
{
//... do some work, publish some messages, etc. ...
ros::spinOnce(); //輪轉一次,返回
r.sleep(); //休眠
}
ros::spinOnce()將會在被呼叫的那一時間點呼叫所有等待的回撥函式.
注意: ros::spin()和ros::spinOnce()函式對單執行緒應用很有意義,目前不會應用於多執行緒.
2.多執行緒輪轉
上面是單執行緒下的訊息回撥函式輪轉,那多執行緒下是什麼樣子?
roscpp庫提供了一些內嵌的支援來從多執行緒中呼叫回撥函式.
1) ros::MultiThreadedSpiner
它是一個阻塞型輪轉器,類似於ros::spin().
可以使用它的構造器來設定執行緒的個數,如果不設定或設成0,它將為每個cpu核心使用一個執行緒.
ros::MultiThreadedSpinner spinner(4); // Use 4 threads
spinner.spin(); // spin() will not return until the node has been shutdown
2)ros::AsyncSpinner
API : http://docs.ros.org/api/roscpp/html/classros_1_1AsyncSpinner.html
更實用的多執行緒輪轉是非同步輪轉器(AsyncSpiner).相對於阻塞的spin()呼叫,它有自己的start()和stop()呼叫
並且在銷燬後將自動停止.對上述MultiThreadedSpiner等效的AsyncSpiner使用如下:
ros::AsyncSpinner spinner(4); // Use 4 threads
spinner.start();
ros::waitForShutdown();
3.CallbackQueue::callAvailable() and callOne()
CallbackQueue API 回撥函式佇列類: http://docs.ros.org/api/roscpp/html/classros_1_1CallbackQueue.html
可以建立一個回撥函式佇列類:
#include <ros/callback_queue.h>
...
ros::CallbackQueue my_queue;
回撥函式佇列類有兩種觸發其內部回撥函式的方法: callAvailable()方法和callOne()方法.
前者將獲取當前可以符合條件的回撥函式,並且全部觸發它們;後者將簡單地觸發佇列中最早的那個回撥函式.
這兩個方法都接受一個可選的timeout超時時間,它們將在此時間之內等待一個回撥函式變得符合條件.
如果這個值是0,那麼,如果佇列中沒有回撥函式,該方法立即返回.
4.高階主題:使用不同的回撥函式佇列
預設的是所有的訊息回撥函式都會被壓入全域性訊息回撥佇列.roscpp允許使用自定義的訊息回撥函式佇列並分別服務.這可以以兩種粒度實現:
1)每個subsceribe(),advertise(),advertiseService(),等
這部分可以使用高階版的方法呼叫原型,使用一個選項結構體指標引數.
2)每個節點控制代碼
這是常見的方法,使用節點控制代碼的setCallbackQueue()方法:
ros::NodeHandle nh;
nh.setCallbackQueue(&my_callback_queue);
這使所有的訊息訂閱者,服務,定時器等的回撥函式都進入my_callback_queue,而非roscpp的預設佇列.
這意味著,ros::spin()和ros::spinOnce()將不會觸發這些回撥函式.
使用者自己必須額外呼叫這些回撥函式.可以使用的是回撥函式佇列類物件的callAvailable()方法和callOne()方法
應用:
將不同的回撥函式分別壓進不同的回撥函式佇列有下面幾個優勢:
1)長時服務:對一個服務的回撥函式安排一個單獨的佇列,然後單獨地使用一個執行緒來呼叫它,可以保證不會阻塞其它回撥函式
2)計算消耗回撥函式:與長時服務相似,為一個費計算時間的回撥函式安排一個單獨的回撥佇列處理,能夠減輕應用的負擔.