Экран pyagme не работает с многопоточностью
Я использую Python 2.7.14 и в настоящее время пытаюсь нарисовать две стороны экрана Pygame одновременно, используя модуль многопроцессорной обработки (2 потока вызывают функции с одного экрана Pygame), но каждый раз я вызываю функцию с экрана (например, screen.get_width()
например) возникает следующая ошибка:
Process Process-1:
Traceback (most recent call last):
File "C:\Python27\lib\multiprocessing\process.py", line 267, in _bootstrap
self.run()
File "C:\Python27\lib\multiprocessing\process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "C:\Python27\multiplayer.py", line 9, in single_core_game
print screen.get_width()
error: display Surface quit
Я знаю, что писать из потоков - не самый элегантный способ, поэтому я буду рад услышать альтернативы.
Спасибо!
1 ответ
В комментариях @Fredrik вы, вероятно, захотите использовать поток, а не отдельный процесс. Потоки имеют общий доступ к памяти, переменным и т. Д., В то время как отдельные процессы имеют отдельную копию с момента запуска дочернего процесса.
Так что, если я могу перефразировать ваш вопрос:
Как мне нарисовать на экране Pygame из нескольких потоков?
И на что краткий ответ: "Вы не делаете".
Как правило, в случае управляемых событиями оконных рабочих столов программы имеют единый поток, который управляет взаимодействием с пользователем, включая обработку ввода и вывода на экран. Конечно, вы можете позвонить screen.blit(...)
из нескольких потоков из-за Python GIL, но это не очень хороший путь для разработки.
Но! Это не означает, что другие потоки и процессы не могут создавать контент для отображения, но затем передают его в основной поток обработчика для этого последнего бита на экран, так как?
Отдельные процессы Python могут общаться друг с другом Client
а также Listener
каналы (это также распространяется на GIL), и, как упоминалось ранее, отдельные потоки могут просто совместно использовать память.
Вот фрагмент кода, который рендерит фоновое изображение на экранную поверхность, а затем отправляет событие в основной поток, когда каждое новое обновление готово. Очевидно, что это тривиальное использование потока, но если бы процесс обновления занимал больше времени, он подходил бы лучше.
Функция потока изначально создает поверхность Pygame, делая ее похожей на 8-битное отображение чернильной черноты пространства. Затем навсегда перемещается по изображению, отправляя копию в виде Pygame.Event в главный поток через очередь событий. Основной поток видит это событие и обновляет его фоновое изображение.
Результат довольно резкий, но это потому, что я помещаю поток в спящий режим на 500 мс на каждую итерацию, чтобы немного замедлить его.
import threading
import pygame
import random
import time
import sys
# Window size
WINDOW_WIDTH=400
WINDOW_HEIGHT=400
DARK_GREY = ( 50, 50, 50 )
SPACE_BLACK = ( 0, 0, 77 )
STAR_WHITE = ( 255, 252, 216 )
### Thread that paints a background image to an off-screen surface
### then posts an event to the main loop when the image is ready
### for displaying.
class BackgroundDrawThread( threading.Thread ):
def __init__( self ):
threading.Thread.__init__(self)
self.daemon = True # exit with parent
self.sleep_event = threading.Event()
self.ofscreen_block = pygame.Surface( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
self.pan_pixels = 5
def makeSpace( self ):
""" Make a starry background """
# Inky blackness of space
self.ofscreen_block.fill( SPACE_BLACK )
# With some (budget-minded) stars
for i in range( 80 ):
random_pixel = ( random.randrange( WINDOW_WIDTH ), random.randrange( WINDOW_HEIGHT ) )
self.ofscreen_block.set_at( random_pixel, STAR_WHITE )
def panSpace( self ):
""" Shift space left, by some pixels, wrapping the image """
rect_to_move = [0, 0, self.pan_pixels, WINDOW_HEIGHT-1]
lost_part = self.ofscreen_block.subsurface( rect_to_move ).copy()
self.ofscreen_block.scroll( dx=-self.pan_pixels, dy=0)
self.ofscreen_block.blit( lost_part, ( WINDOW_WIDTH-1-self.pan_pixels,0) )
def run( self ):
""" Do Forever (or until interuppted) """
# Make the space backdrop
self.makeSpace()
while ( True ):
if ( True == self.sleep_event.wait( timeout=0.5 ) ):
break # sleep was interrupted by self.stop()
else:
# Rotate space a bit
self.panSpace()
# Message the main-thread that space has been panned.
new_event_args = { "move": self.pan_pixels, "bitmap": self.ofscreen_block.copy() }
new_event = pygame.event.Event( pygame.USEREVENT + 1, new_event_args )
pygame.event.post( new_event )
def stop( self ):
self.sleep_event.set() # interrupt the wait
self.join()
### MAIN
pygame.init()
pygame.display.set_caption("Threaded Paint")
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE )
# Start the render-my-background thread
thread1 = BackgroundDrawThread()
thread1.start()
background = None
# Main paint / update / event loop
done = False
clock = pygame.time.Clock()
while ( not done ):
# Handle Events
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.USEREVENT + 1 ):
background = event.bitmap
print( "Space folded by %d pixels" % ( event.move ) )
# Paint the window
if ( background != None ): # wait for the first backgroun to be ready message
WINDOW.blit( background, (0,0) )
pygame.display.flip()
# Max FPS
clock.tick_busy_loop(60)
thread1.stop()
pygame.quit()
Хм, глядя на анимацию, мне кажется, что у меня ошибка "1 к 1" в свертывании пространства. Я думаю, что код нуждается в большем количестве специй.