编者注:学霸出没请注意!本课教程由某某学霸翻译,详见页脚。
首先声明,在这里我们会实现视频多线程播放。实践中你绝不会这样,这是一种不良的设计,有时甚至导致你的系统动荡。我们用到它是因为在这里它只是一个稳定的小玩意,用以演示信号量的操作。
现在让我们回到主题上来,下面程序中的两个线程将会同时进行表面位块传输并更新屏幕。两个线程同时对同一数据进行操作是很危险的隐患,这时就要用到信号量了,信号量将会只允许其中一个线程对屏幕进行操作。
现在让我们回到主题上来,下面程序中的两个线程将会同时进行表面位块传输并更新屏幕。两个线程同时对同一数据进行操作是很危险的隐患,这时就要用到信号量了,信号量将会只允许其中一个线程对屏幕进行操作。
//将要用到的线程 SDL_Thread *threadA = NULL; SDL_Thread *threadB = NULL; //保护性的信号量 SDL_sem *videoLock = NULL;
程序最上方定义了两个将用到的线程和一个相关的信号量。
bool init() { //初始化所有SDL子系统 if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ) { return false; } //设置屏幕 screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE ); //如果设置屏幕出错 if( screen == NULL ) { return false; } //初始化SDL_ttf if( TTF_Init() == -1 ) { return false; } //创建信号量 videoLock = SDL_CreateSemaphore( 1 ); //设置窗口标题 SDL_WM_SetCaption( "Testing Threads", NULL ); //如果一切初始化良好 return true; }
信号量在使用之前必须使用
你也许会对1这个参数感到疑问,后面会解释。
SDL_CreateSemaphore()
创建。
你也许会对1这个参数感到疑问,后面会解释。
int blitter_a( void *data ) { //Y坐标 int y = 10; //遍历表面 for( int b = 0; b < 5; b++ ) { //等待 SDL_Delay( 200 ); //显示表面 show_surface( ( ( SCREEN_WIDTH / 2 ) - text[ b ]->w ) / 2, y, text[ b ] ); //向下移动 y += 100; } return 0; } int blitter_b( void *data ) { //Y坐标 int y = 10; //遍历表面 for( int b = 0; b < 5; b++ ) { //等待 SDL_Delay( 200 ); //显示表面 show_surface( ( SCREEN_WIDTH / 2 ) + ( ( ( SCREEN_WIDTH / 2 ) - text[ b ]->w ) / 2 ), y, text[ b ] ); //向下移动 y += 100; } return 0; }
上面是线程的函数。它们都是传递一组表面并让其显示在屏幕上。
可以发现,我们并没有使用平时的
接着我们让多线程同时运行,同时要防止它们同时操作屏幕。为此我们用到信号量来保护
blitter_a()
传递表面至屏幕左侧,blitter_b()
在右侧。
可以发现,我们并没有使用平时的
apply_surface()
函数,而是show_surface()
,一个修改过的apply_surface()
函数。它将SDL_BlitSurface()
提交表面和SDL_Flip()
更新屏幕实现在一个函数内。待会你会见识到。
接着我们让多线程同时运行,同时要防止它们同时操作屏幕。为此我们用到信号量来保护
show_surface()
函数。
void show_surface( int x, int y, SDL_Surface* source ) { //锁定 SDL_SemWait( videoLock ); //保存坐标 SDL_Rect offset; //获取坐标 offset.x = x; offset.y = y; //Blit SDL_BlitSurface( source, NULL, screen, &offset ); //更新屏幕 SDL_Flip( screen ); //解锁 SDL_SemPost( videoLock ); }
这里show_surface()函数将会传递位表面然后更新屏幕。在头尾分别是
当另一个线程尝试进入时,它必须等待:
直到
然后下一个线程执行并锁上信号量:
因为
之前提到过
SDL_SemWait()
和SDL_SemPost()
。
SDL_SemWait()
用来锁上信号量:
当另一个线程尝试进入时,它必须等待:
直到
SDL_SemPost()
被调用解锁信号量:
然后下一个线程执行并锁上信号量:
因为
SDL_BlitSurface()
和SDL_Flip()
受信号量保护,所以同一时间只有一个线程能够调用视频函数,不会发生冲突。
之前提到过
init()
函数中SDL_CreateSemaphore()
的参数1,1代表信号量上锁前最多能有几个线程通过。我们也可以让2个线程通过,这里不再示例。大多数时候信号量上锁前,都只会让一个线程执行。
//显示背景 show_surface( 0, 0, background ); //创建并运行线程 threadA = SDL_CreateThread( blitter_a, NULL ); threadB = SDL_CreateThread( blitter_b, NULL ); //等待线程结束 SDL_WaitThread( threadA, NULL ); SDL_WaitThread( threadB, NULL ); //当用户还没退出 while( quit == false ) { //如果有事件需要处理 if( SDL_PollEvent( &event ) ) { //如果用户叉掉了窗口 if( event.type == SDL_QUIT ) { //退出程序 quit = true; } } }
在
然后我们运行两个线程并且通过
main()
线程中,所有东西加载和初始完毕后,背景会呈现在屏幕上。
然后我们运行两个线程并且通过
SDL_WaitThread()
来等待线程结束。线程结束后再等待用户的退出。
void clean_up() { //清除信号量 SDL_DestroySemaphore( videoLock ); //释放表面 SDL_FreeSurface( background ); //释放文本 for( int t = 0; t < 5; t++ ) { SDL_FreeSurface( text[ t ] ); } //关闭字体 TTF_CloseFont( font ); //退出SDL_ttf TTF_Quit(); //退出SDL SDL_Quit(); }
不要忘记使用完后清除信号量。
使用
使用
SDL_DestroySemaphore()
函数可以清除信号量。