1. 程式人生 > >ROS學習之 cpp回撥函式和輪轉(spin)

ROS學習之 cpp回撥函式和輪轉(spin)


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)計算消耗回撥函式:與長時服務相似,為一個費計算時間的回撥函式安排一個單獨的回撥佇列處理,能夠減輕應用的負擔.