pygame屏幕不适用于多线程
pyagme screen not working with multithreading
我正在使用 python 2.7.14,目前正在尝试使用多处理模块同时绘制到 pygame 屏幕的两侧(2 个线程正在从单个 [=19 调用函数=] 屏幕)但每次我从屏幕调用函数时(例如 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
我知道从线程中写入并不是最优雅的方式,所以我很乐意听到替代方案。
谢谢!
正如@Fredrik 评论的那样,您可能想要使用线程,而不是单独的进程。线程共享对内存和变量等的访问,而单独的进程从子进程启动的那一刻起就有一个单独的副本。
所以,如果我可以重新措辞你的问题:
How do I draw to the pygame screen from multiple threads?
简短答案是:"You don't"。
通常,对于事件驱动的、基于 window 的桌面,程序有一个线程来管理与用户的交互,包括处理屏幕的输入和输出。当然,由于 Python GIL,您 可能 能够从多个线程调用 screen.blit(...)
,但这不是一个好的设计路径。
但是!这并不是说其他线程和进程不能 创建 显示内容,而是将其交给主处理程序线程以将最终 blit 发送到屏幕,那么如何呢?
单独的 python 进程可以通过 Client
和 Listener
pipes 相互通信(这也绕过 GIL),并且如前所述,单独的线程只能共享内存。
这是一段笨拙的代码,将背景图像渲染到屏幕外表面,然后在每次新更新准备就绪时将事件发布到主线程。显然这是一个线程的琐碎使用,但如果有一个更耗时的更新过程,它会更适合。
线程函数最初创建了一个 pygame 表面,使其看起来像是 space 漆黑的 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()
嗯,看动画,我好像在 space 的折叠中有一个 off-by-1 错误。我想代码需要更多 spice.
我正在使用 python 2.7.14,目前正在尝试使用多处理模块同时绘制到 pygame 屏幕的两侧(2 个线程正在从单个 [=19 调用函数=] 屏幕)但每次我从屏幕调用函数时(例如 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
我知道从线程中写入并不是最优雅的方式,所以我很乐意听到替代方案。
谢谢!
正如@Fredrik 评论的那样,您可能想要使用线程,而不是单独的进程。线程共享对内存和变量等的访问,而单独的进程从子进程启动的那一刻起就有一个单独的副本。
所以,如果我可以重新措辞你的问题:
How do I draw to the pygame screen from multiple threads?
简短答案是:"You don't"。
通常,对于事件驱动的、基于 window 的桌面,程序有一个线程来管理与用户的交互,包括处理屏幕的输入和输出。当然,由于 Python GIL,您 可能 能够从多个线程调用 screen.blit(...)
,但这不是一个好的设计路径。
但是!这并不是说其他线程和进程不能 创建 显示内容,而是将其交给主处理程序线程以将最终 blit 发送到屏幕,那么如何呢?
单独的 python 进程可以通过 Client
和 Listener
pipes 相互通信(这也绕过 GIL),并且如前所述,单独的线程只能共享内存。
这是一段笨拙的代码,将背景图像渲染到屏幕外表面,然后在每次新更新准备就绪时将事件发布到主线程。显然这是一个线程的琐碎使用,但如果有一个更耗时的更新过程,它会更适合。
线程函数最初创建了一个 pygame 表面,使其看起来像是 space 漆黑的 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()
嗯,看动画,我好像在 space 的折叠中有一个 off-by-1 错误。我想代码需要更多 spice.