回調函數ros::spin()與ros::spinOnce()


 

ros::spin()

 

這句話的意思是循環且監聽反饋函數(callback)。循環就是指程序運行到這里,就會一直在這里循環了。監聽反饋函數的意思是,如果這個節點有callback函數,那寫一句ros::spin()在這里,就可以在有對應消息到來的時候,運行callback函數里面的內容。
就目前而言,以我愚見,我覺得寫這句話適用於寫在程序的末尾(因為寫在這句話后面的代碼不會被執行),適用於訂閱節點,且訂閱速度沒有限制的情況。

 

ros::spinOnce()

 

這句話的意思是監聽反饋函數(callback)。只能監聽反饋,不能循環。所以當你需要監聽一下的時候,就調用一下這個函數。
這個函數比較靈活,尤其是我想控制接收速度的時候。配合ros::ok()效果極佳

 

ROS的主循環中需要不斷調用ros::spin()或ros::spinOnce(),兩者區別在於前者調用后不會再返回,而后者在調用后還可以繼續執行之后的程序。

在使用ros::spin()的情況下,一般來說在初始化時已經設置好所有消息的回調,並且不需要其他背景程序運行。這樣一來,每次消息到達時會執行用戶的回調函數進行操作,相當於程序是消息事件驅動的;而在使用ros::spinOnce()的情況下,一般來說僅僅使用回調不足以完成任務,還需要其他輔助程序的執行:比如定時任務、數據處理、用戶界面等。

關於消息接收回調機制在ROS官網上略有說明 (callbacks and spinning)。總體來說其原理是這樣的:除了用戶的主程序以外,ROS的socket連接控制進程會在后台接收訂閱的消息,所有接收到的消息並不是立即處理,而是等到spin()或者spinOnce()執行時才集中處理。所以為了保證消息可以正常接收,需要尤其注意spinOnce()函數的使用 (對於spin()來說則不涉及太多的人為因素)。

I. 對於速度較快的消息,需要注意合理控制消息隊列及spinOnce()的時間。例如,如果消息到達的頻率是100Hz,而spinOnce()的執行頻率是10Hz,那么就要至少保證消息隊列中預留的大小大於10。

II. 如果對於用戶自己的周期性任務,最好和spinOnce()並列調用。即使該任務是周期性的對於數據進行處理,例如對接收到的IMU數據進行Kalman濾波,也不建議直接放在回調函數中:因為存在通信接收的不確定性,不能保證該回調執行在時間上的穩定性。

 

// 示例代碼
ros::Rate r(100);
 
while (ros::ok())
{
  libusb_handle_events_timeout(...); // Handle USB events
  ros::spinOnce();                   // Handle ROS events
  r.sleep();
}

 

III. 最后說明一下將ROS集成到其他程序架構時的情況。有些圖形處理程序會將main()包裹起來,此時就需要找到一個合理的位置調用ros::spinOnce()。比如對於OpenGL來說,其中有一個方法就是采用設置定時器定時調用的方法:

 

// 示例代碼
void timerCb(int value) {
  ros::spinOnce();
}
glutTimerFunc(10, timerCb, 0);
glutMainLoop(); // Never returns

 

消息到來並不會立即執行消息處理回調函數,而是在調用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::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)計算消耗回調函數:與長時服務相似,為一個費計算時間的回調函數安排一個單獨的回調隊列處理,能夠減輕應用的負擔.

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM