トピックは、ROS の 3 つの通信方法の 1 つであり、最も基本的で一般的に使用されます。この記事では、ROS トピック通信の背後にあるデータ スループット メカニズムをより詳細かつ詳細に紹介します。
出版社
ROSでトピックを公開する機能はこんな感じ
ros::Publisher advertise(const std::string& topic, uint32_t queue_size, bool latch = false);
Parameters:
topic: Topic to advertise on
queue_size: Maximum number of outgoing messages to be queued for delivery to subscribers
latch: (optional) If true, the last message published on this topic will be saved and sent to new subscribers when they connect
パラメータは 3 つあります:topic
パブリッシュするトピック、queue_size
パブリッシャ キューに保存できるメッセージの数、latch
ロック (パブリッシュを停止した後に最後のメッセージを保存し、以前に保存したメッセージを新しいサブスクライバに送信するなど)新しい加入者サブスクリプションがあります。 加入者。送信するデータが予想される周波数に従って送信できるかどうか、フレームロスが発生するかどうかなどに関連する次の点について話しましょう
。queue_size
事例分析
ros::init(argc, argv, "talker");
ros::NodeHandle handle;
ros::Publisher chatter_pub = handle.advertise<std_msgs::String>("chatter", 10);
ros::Rate loop_rate(100);
int count = 0;
std::stringstream ss;
std_msgs::String msg;
while (ros::ok())
{
ss<<"Message ["<<count<<"]";
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
loop_rate.sleep();
++count;
// ss.str("");
}
たとえば、上の例ではpublish()
サイクル周波数を 100Hz に設定しましたが、rostopic hz <topic>
このトピックのリリース周波数は 100Hz 未満であり (下図に示すように)、実際のリリース周波数は設定周波数と一致しません。
これは、私が使用しているメッセージがstringstream
消去されずに徐々に蓄積され、文字列がますます長くなるため、公開されるメッセージがますます複雑になり、発行者スレッドの処理にますます時間がかかるためです。発行頻度は徐々に減少しています。しかし、リリースされるニュースが非常に単純なものであれば、それでもついていくことは可能であり、一般的に言えば、publish()
公開の頻度と頻度はほぼ同等です。
では、この周波数のずれの背後にあるものは何でしょうか?
出版社の裏側
パブリッシャーキューはコールバックキューと同様のキューですが、
publish()
関数が呼び出されるたびにいっぱいになるパブリッシュされたメッセージをキューに入れるためのキューです。
パブリッシャー キューからメッセージを取得し、トピックのサブスクライバーが存在する場合はそのメッセージを送信する役割を担う別のスレッド (パブリッシャー スレッド) があります。publish()
パブリッシャ スレッドがメッセージを送信できる速度よりも早く を呼び出すと、パブリッシャ キューにメッセージが蓄積され始め、指定されたキュー サイズ(この場合は 1000)を超えると、古いメッセージが新しいメッセージで上書きされ始めます。メッセージ。
呼び出されるたびに、publish()
メッセージがパブリッシャー メッセージ キュー (以下、PMQ) に入れられ、その後、別のスレッド パブリッシャー スレッドが PMQ からメッセージを取り出し、実際にパブリッシュします。
私たちが呼ぶサイクル頻度は予想されるpublish()
公開頻度であり、パブリッシャー スレッドによって処理される頻度は実際の。
一般に、発行者スレッドの処理頻度は呼び出しの頻度よりも高いためpublish()
、メッセージの発行頻度がpublish()
サイクル レートになります。ただし、publish()
サイクル頻度が高すぎてパブリッシャー スレッドの処理速度を超える場合 (またはパブリッシャー スレッドの処理速度がその速度より低い場合publish()
)、メッセージは PMQ に蓄積され、queue_size を超えると、古いメッセージは失われます。
したがって、発行されるニュースがより複雑でpublish()
頻度が高い場合は、大きく設定することをお勧めしますqueue_size
。そうしないと、データが失われます。もちろん使用するシーンにもよりますが、コマ落ちしても問題ないがデータの更新が必要なシーンもあります。
購読者
ROS でトピックを購読する機能は次のとおりです。
ros::Subscriber subscribe(const std::string& topic, uint32_t queue_size, <callback, which may involve multiple arguments>, const ros::TransportHints& transport_hints = ros::TransportHints());
topic
まだわかりやすいので、ぜひ購読したいトピックです。callback
平和について話しましょうqueue_size
。
折り返し電話
サブスクライバの初期化時にコールバック関数 callback を指定し、コールバック関数の登録を行います。
では、コールバック関数の登録とは何でしょうか? 私の理解は次のとおりです。馴染みのある友人はスキップできます。
コールバックは、どのコールバック関数を呼び出す必要があるかを呼び出し元に伝えることです。実際には、この関数の関数ポインタを呼び出し元に渡すことです。たとえば、この例では、コールバック ポインタは、subscribe 関数を通じてサブスクライバに渡されます
subscribe(callback)
。日常生活で例えてみましょう。たとえば、あなた(コールバック機能)がユニットに来たばかりで、上司(発信者)に「用事がある場合は電話してください」と伝え、顔を見せるプロセスが登録です。コールバック関数の。その後、リーダーが実際に何かを求めてあなたのところにやって来て、コールバック関数を呼び出しました。
ROS のコールバック処理に関連するオブジェクトには、コールバック キューとスピナーの 2 種類があります。
サブスクライバ ノードが初期化されると、メッセージを受信するためのスレッド (受信スレッド) が作成され、各サブスクライバはメッセージを受信するためのキュー サブスクライバ メッセージ キュー (SMQ) を持ちます。同様に、コールバック関数を格納するためのコールバック キューがあります。サブスクライバがメッセージを受信するたびに、コールバックがコールバック キューに入れられます。
スピナーは、コールバック キューに含まれるコールバックを呼び出す機能を持つオブジェクトです。コールバック キューは、各サブスクライバがメッセージを受信するたびに、どの種類のメッセージがどのコールバック (およびどの引数で) を呼び出す必要があるかを解決することで要素を追加するオブジェクトです。
コールバック キュー/スピニングは、roscpp の内部ネットワーク通信には影響しません。これらは、ユーザーのコールバックが発生した場合にのみ影響します。コールバックの処理速度とメッセージの到着速度がメッセージがドロップされるかどうかを決定するため、これらはサブスクリプション キューに影響します。
roscpp は 3 つのコールバックをサポートします
-
関数
void callback(const std_msgs::StringConstPtr& str) { } ros::Subscriber sub = nh.subscribe("my_topic", 1, callback);
-
クラスメソッド
void Foo::callback(const std_msgs::StringConstPtr& message) { } Foo foo_object; ros::Subscriber sub = nh.subscribe("my_topic", 1, &Foo::callback, &foo_object);
-
ファンクター オブジェクト。ここでは紹介しません。
SMQ のコールバック処理は Spinner によって実装されます。
スピナー
ロス::スピン()
ros::spin()
別のスレッド スピナー スレッドが作成され、コールバックがキューから取り出され、連続ループで実行されます。
メインプログラムにはループがありませんが、メインプログラムros::spin()
にはループがあります。サブスクライバはメッセージを受信し、コールバック関数をコールバック キューに入れますが、実際の実行はspin()で実行されます。上記のパブリッシャー スレッドと同様に、スピナー スレッドがコールバックを処理する速度は、コールバック関数の複雑さによって決まります。
ros:spin()
これは別個のスレッドであり、このスレッドはループ内のコールバック関数を一度に 1 つずつ処理します。特定のサイクル時間はコールバックに従って決定されますが、これは一般に受信機が情報を受信する頻度と一致しません。レシーバーが情報を受信するのが速い場合は、コールバック キューを非常に小さく設定できます。スピナーがレシーバーが情報を受信するよりもコールバックを処理するのが遅い場合は、コールバック キューにコールバックが蓄積され始めます。現時点では、これが最適です。 queue_size を大きく設定します。
場合によっては、スピナー スレッドが実際に処理できない場合があります。どうすればよいですか? スピナー スレッドを二重にすることができます。マルチスレッド スピナーを使用してコールバックを並列処理します。
スピナーを実装するには合計 3 つの方法があります。スピナーの 3 つの実装を参照してください。
マルチスレッドスピニング
ros::MultiThreadedSpinner()
マルチスレッドには と の2 種類がありますros::AsyncSpinner()
。一般用途AsyncSpinner()
。
マルチスピナースレッド
ros::MultiThreadedSpinner spinner(2); // 开两个spinner并行处理
spinner.spin();
ブロック呼び出しの代わりにと呼び出しspin()
があり、破棄されると自動的に停止します。上記の例と同等の の使用法は次のとおりです。start()
stop()
AsyncSpinner
MultiThreadedSpinner
ros::AsyncSpinner spinner(4); // Use 4 threads
spinner.start();
ros::waitForShutdown();