這句話的意思是循環且監聽反饋函數(callback)。循環就是指程序運行到這裏,就會一直在這裏循環了。監聽反饋函數的意思是,若是這個節點有callback函數,那寫一句ros::spin()在這裏,就能夠在有對應消息到來的時候,運行callback函數裏面的內容。
就目前而言,以我愚見,我以爲寫這句話適用於寫在程序的末尾(由於寫在這句話後面的代碼不會被執行),適用於訂閱節點,且訂閱速度沒有限制的狀況。html
這句話的意思是監聽反饋函數(callback)。只能監聽反饋,不能循環。因此當你須要監聽一下的時候,就調用一下這個函數。
這個函數比較靈活,尤爲是我想控制接收速度的時候。配合ros::ok()效果極佳node
ROS的主循環中須要不斷調用ros::spin()或ros::spinOnce(),二者區別在於前者調用後不會再返回,然後者在調用後還能夠繼續執行以後的程序。api
在使用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來講,其中有一個方法就是採用設置定時器定時調用的方法:socket
// 示例代碼 void timerCb(int value) { ros::spinOnce(); } glutTimerFunc(10, timerCb, 0); glutMainLoop(); // Never returns
消息到來並不會當即執行消息處理回調函數,而是在調用ros::spin()以後,才進行消息處理的輪轉,消息回調函數統一處理訂閱話題的消息。函數
roscpp不會在你的應用中明確一個線程模型:也就是說即便roscpp會在幕後使用多線程管理網絡連接,調度等,但它不會將本身的線程暴露在你的應用中。oop
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)計算消耗回調函數:與長時服務類似,爲一個費計算時間的回調函數安排一個單獨的回調隊列處理,可以減輕應用的負擔.