Ubuntu16.04 ROS Kinetic - 통신3) 처리 지연 문제
1. 문제 : 도착하는 데이터를 미처 처리하지 못하면 어떻게 되는가?
받는 쪽이 버벅대게 만들어 놓고 데이터를 왕창 보낸다.
구독자의 콜백함수 안에 시간이 많이 걸리는 코드를 넣어서 토픽 처리에 시간이 걸리도록 한다.
콜백함수가 끝나지 않았는데 토픽이 새로 도착하면 어떻게 되는가?
1-1. 파이썬 파일 작성
sender_overflow.py
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32
rospy.init_node('Sender')
pub = rospy.Publisher('my_topic', Int32, queue_size=10)
rate = rospy.Rate(1000)
count = 1
while not rospy.is_shutdown():
pub.publish(count)
count += 1
rate.sleep()
receiver_overflow.py
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32
def callback(msg):
rospy.sleep(2)
print msg.data
rospy.init_node('Receiver')
sub = rospy.Subscriber('my_topic', Int32, callback, queue_size=1)
rospy.spin()
sr_overflow.launch
<launch>
<node pkg="msg_send" type="sender_overflow.py" name="Sender"/>
<node pkg="msg_send" type="receiver_overflow.py" name="Receiver" output="screen"/>
</launch>
1-2. 빌드 및 실행
빌드
cm
실행
roslaunch msg_send sr_overflow.launch
2. 현상 확인
1-2와 같이 결과가 나온다.
즉 subscriber의 콜백함수가 끝나지 않았는데, publisher가 계속해서 토픽을 발행한다면 subscriber는 처리하지 못한 토픽은 버리고 새로 들어오는 토픽을 처리한다.
3. 원인 분석
이번 문제는 원인 분석과 실행의 순서가 반대로 되었는데,
왜냐하면 이러한 현상을 만들기 위해 내가 일부러 소스코드를 위와 같이 작성했기 때문이다.
모순적이지만, 아무튼 원인을 분석해보면 원인은 Subscriber의 queue_size를 1로 지정했기 때문이다.
4. 해결책 및 적용 결과
가장 좋은 해결책은 callback함수 안의 시간을 오래 잡아먹는 코드를 제거하는 것이다.
하지만, 이 문제는 도착하는 데이터를 미처 처리하지 못한 경우에 대한 문제이기 때문에 다른 해결책을 찾아보자.
1) 큐의 크기를 아주 크게 (infinite) 하면 어느 정도 해결이 가능하다.
Subscriber클래스에서 queue_size 속성을 지정해주지 않으면 디폴트는 None으로 무한대를 의미한다.
pub = rospy.Publisher('my_topic', Int32, queue_size=10)
sub = rospy.Subscriber('my_topic', Int32, callback)
첫 1~57까지의 토픽은 받지 못했지만, 이후는 모든 토픽을 느리지만 천천히 받아온다.
2) 만약, 통신1에서처럼 Publisher의 latch속성을 이용하면 어떻게 되는지 확인해보자.
pub = rospy.Publisher('my_topic', Int32, queue_size=10, latch=True)
sub = rospy.Subscriber('my_topic', Int32, callback, queue_size=1)
latch 속성을 켜도 지연되는 토픽은 버려졌다.
3) Subscriber의 tcp_nodelay속성을 True 로 설정하였다.
tcp_nodelay 파라미터가 True이면 publisher에게 TCP_NODELAY를 요청한다. 메시지 데이터의 타임 스탬프에 의존하는 것이 더 낫기 때문에 이 옵션의 사용은 일반적으로 권장되지 않는다고 한다.
pub = rospy.Publisher('my_topic', Int32, queue_size=10)
sub = rospy.Subscriber('my_topic', Int32, callback, queue_size=1, tcp_nodelay=True)
tcp의 nodelay는 시간 지연이 생기지 않도록 하는 속성인 듯 하다. 순서가 맞지 않는 데이터를 보정하는 것에 대한 속성이 아닌 것 같다.
4) 해결책은 아니지만, 구독자 큐 size < 발행자 큐 size 인 경우에는 결과가 어떻게 나오는지 궁금하여 실행해 보았다.
pub = rospy.Publisher('my_topic', Int32, queue_size=10)
sub = rospy.Subscriber('my_topic', Int32, callback, queue_size=5)
첫 몇개의 토픽을 잃어버리고, 이후 토픽에 대해서도 완전히 이어서 출력하지 못한다. 정확히 5개(=구독자 큐 size)씩 이어서 출력하는 것을 확인할 수 있다.
5. 고찰
결국, 처리 지연 문제를 해결하기 위해서는 queue_size=None으로 하여 구독자가 무한한 크기의 큐를 사용해야 한다.
하지만 그렇게 하더라도 처음 몇개의 토픽을 받아올 수는 없었다.
6. 참고 자료
1) Subscriber 클래스
docs.ros.org/en/electric/api/rospy/html/rospy.topics.Subscriber-class.html
rospy.topics.Subscriber
__init__(self, name, data_class, callback=None, callback_args=None, queue_size=None, buff_size=65536, tcp_nodelay=False) (Constructor) source code Constructor. NOTE: for the queue_size and buff_size parameters, rospy does not attempt to do intelligent m
docs.ros.org
Subscriber의 큐 size를 어떻게 설정하는지 몰라서 약간 헤메었다.
queue_size=원하는_큐_사이즈 로 설정하면 되는데, 여기서 파라미터들의 순서가 바뀌면 안된다.
즉, sub = rospy.Subscriber('my_topic', Int32, queue_size=5, callback) 와 같이 작성하면 실행되지 않는다.