在实时动画上线程化生产者和消费者计时

问题描述 投票:0回答:1

我正在从我的 com 端口读取每 100us 一次的数据(16 字节/4 个浮点数)。同时,我使用当时的数据以 60fps(0.016667 秒/帧)的速率进行动画实时更新。我已附上下面的代码。

整个系统背后的想法是 readdata 函数使用 pyserial 被动读取和解析该数据。但是,我只想使用每 0.016667 秒显示一次的数据。当 readdata 函数运行时,我的动画会并行更新帧和所有内容。现在我的思考过程是有一个标志,当我需要抓取数据来更新框架时,该标志会升起。但是,我当前的代码使动画崩溃。我是线程新手,所以有什么关于更好的方法的建议,我可能会错过理解的事情,或者解决这个问题的方法吗?

class Pipeline:
"""
Class to allow a single element pipeline between producer and consumer.
"""
def __init__(self):
    self.message = 0
    self.flag = 0
    self.producer_lock = threading.Lock()
    self.consumer_lock = threading.Lock()
    self.flag_set_lock = threading.Lock()
    self.flag_get_lock = threading.Lock()
    self.flag_get_lock.acquire()
    self.consumer_lock.acquire()

def get_message(self, name):
    self.consumer_lock.acquire()
    message = self.message
    self.producer_lock.release()
    return message

def set_message(self, message, name):
    self.producer_lock.acquire()
    self.message = message
    self.consumer_lock.release()

def consumer_get_flag(self, name):
    self.flag_get_lock.acquire()
    flag = self.flag
    self.flag_set_lock.release()
    return flag

def set_flag(self, flag, name):
    self.flag_set_lock.acquire()
    self.flag = flag
    self.flag_get_lock.release()

def producer_get_flag(self,name):
    return self.flag

# reading port function
def readport(pipeline):
# global flag
#read incoming data comming in every 100us
with serial.Serial('COM3',baudrate=3000000,bytesize=8, timeout=None, stopbits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE, rtscts=True, dsrdtr=False) as s:
    print('Opened',s.name)
    while True:
        res=s.read(16)
        #parse data
        ypos=struct.unpack('f',res[0:4])[0]
        xpos=struct.unpack('f',res[4:8])[0]
        yamp=struct.unpack('f',res[8:12])[0]
        xamp=struct.unpack('f',res[12:16])[0]
        #flag every 60 fps to set the pipline
        flag = pipline.consumer_get_flag('Flag')
        if  flag == 1:
            pipeline.set_message([xpos,ypos], "Data")
            pipeline.set_flag(0,'Flag Down')
        if xamp != 5.0:
            print('values', [xpos,ypos,xamp,yamp])
            break
            # print('something wrong')

def animation(pipline):
# global flag
running = True
simtime = 0
prevx = 0
prevy = 0
while running:
    for event in pygame.event.get():
        if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
            # The user closed the window or pressed escape
            running = False
    screen.fill((255, 255, 255, 255))
    # Draw the world
    for body in world.bodies:
        for fixture in body.fixtures:
            fixture.shape.draw(body, fixture)
    #update robot position from the data
    pipline.set_flag(1,'Flag Up')
    if pipline.producer_get_flag('current flag') == 1: 
        xpos = prevx
        ypos = prevy
    else:
        xpos, ypos = pipline.get_message('Robot')
        prevx = xpos
        prevy = ypos
    robot1.position = b2Vec2(xpos,ypos+12)
    world.Step(TIME_STEP, 10, 10)
    simtime = simtime + TIME_STEP
    print(simtime)
    # Flip the screen and try to keep at the target FPS
    pygame.display.flip()
    clock.tick(TARGET_FPS)
pygame.quit()
print('Done!')


# --- main game loop ---

pipline = Pipeline()
flag = 0
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
    executor.submit(readport, pipline)
    executor.submit(animation,pipline)`
python-multithreading timing
1个回答
0
投票

您可以更简单地做到这一点。在Python中,线程共享内存。如果您小心的话,可以跨线程使用相同的对象。我认为你可以利用这一点并消除整个 Pipeline 类。你不需要它。

串口数据总是被操作系统缓冲的,所以无论如何你都必须不断地读取端口,否则字符会堆积在缓冲区中。由于端口向您发送数据的速度比您处理数据的速度快,因此大部分数据将被丢弃。所以只需将端口的最新数据放入一些变量中即可。大多数时候,即使您从未使用过最后一组数据,您也会覆盖它。这根本没有坏处。

当您的动画循环准备好接收另一组数据时,它只需从相同的变量中获取它即可。数据将会在那里。不需要任何标志或同步。

一个微妙之处是数据可能作为一个集合出现:x 和 y 坐标。他们必须一起更新。因此,在下面的清单中,我使用一个简单的线程。RLock 来确保两个变量不能一次访问一个。

就是这个想法。您还需要一些逻辑来处理用户退出或串行数据出现问题时的情况。我对此进行了尝试。由于我无法运行您的程序,以下只是该想法的示意图。您可能需要在运行之前进行一些调试。

class Position:
    def __init__(self):
        self.xpos = 0
        self.ypos = 0
        self.lock = threading.RLock()
        
    def set(self, xpos, ypos):
        with self.lock:
            self.xpos = xpos
            self.ypos = ypos
            
    def get(self):
        with self.lock:
            return self.xpos, self.ypos
        
POSITION = Position()

running = True

# reading port function
def readport():
    #read incoming data comming in every 100us
    with serial.Serial('COM3', baudrate=3000000, bytesize=8, timeout=None,
                       stopbits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE,
                       rtscts=True, dsrdtr=False) as s:
        print('Opened',s.name)
        while running:
            res=s.read(16)
            #parse data
            ypos = struct.unpack('f',res[0:4])[0]
            xpos = struct.unpack('f',res[4:8])[0]
            yamp = struct.unpack('f',res[8:12])[0]
            xamp = struct.unpack('f',res[12:16])[0]
            if xamp != 5.0:
                print('values', [xpos,ypos,xamp,yamp])
                break
            POSITION.set(xpos, ypos)

def animation_wrapper(port_thread):
    global running
    try:
        animation(port_thread)
    finally:
        pygame.quit()
        print('Done!')
        running = False
        
def animation(port_thread):
    simtime = 0
    prevx = 0
    prevy = 0
    while port_thread.is_alive():
        for event in pygame.event.get():
            if event.type == QUIT or (event.type == KEYDOWN and
                                      event.key == K_ESCAPE):
                # The user closed the window or pressed escape
                return
        screen.fill((255, 255, 255, 255))
        # Draw the world
        for body in world.bodies:
            for fixture in body.fixtures:
                fixture.shape.draw(body, fixture)
        #update robot position from the data
        xpos, ypos = POSITION.get()
        robot1.position = b2Vec2(xpos, ypos+12)
        world.Step(TIME_STEP, 10, 10)
        simtime = simtime + TIME_STEP
        print(simtime)
        # Flip the screen and try to keep at the target FPS
        pygame.display.flip()
        clock.tick(TARGET_FPS)

# --- main game loop ---
port_thread = threading.Thread(target=port_thread)
port_thread.start()
# wait for port_thread to start
while not port_thread.is_running():
    time.sleep(0.02)
animation_thread = threading.Thread(target=animation_wrapper,
                                    args=(port_thread))
animation_thread.start()

我还修复了你的缩进。使用 StackOverflow 时请小心这一点,尤其是对缩进敏感的 Python 代码。

© www.soinside.com 2019 - 2024. All rights reserved.