|
想要查看内容赶紧注册登陆吧!
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
2.1:竞争条件(Race Conditions)
我们在前面将一个普通函数调用转换成了用线程调用,这意味着我们可以“同时”调用两个以上的线程。例如,我们希望在屏幕的另外一个位置也播放这段简单的动画,我们只需要添加一个线程的调用就可以了。
int main(int argc ,char* argv[])
{
//Create a SDL screen.
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const Uint32 SCREEN_FLAGS = 0; //SDL_FULLSCREEN | SDL_DOUBLEBUF | SDL_HWSURFACE
const std::string WINDOW_NAME = "Amn Test";
ScreenSurface screen(SCREEN_WIDTH, SCREEN_HEIGHT, WINDOW_NAME, 0, SCREEN_FLAGS);
PictureSurface bg("./images/background.png", screen);
bg.blit(0);
screen.flip();
AmnArg test1(0, 250, 600, 250, screen);
SDL_Thread* thread1 = SDL_CreateThread(amn, (void*)&test1);
AmnArg test2(0, 0, 600, 0, screen);
SDL_Thread* thread2 = SDL_CreateThread(amn, (void*)&test2);
SDL_Event gameEvent;
bool gameOver = false;
while ( gameOver == false ){
while ( SDL_PollEvent(&gameEvent) != 0 ){
if ( gameEvent.type == SDL_QUIT ){
gameOver = true;
}
if ( gameEvent.type == SDL_KEYDOWN ){
if ( gameEvent.key.keysym.sym == SDLK_ESCAPE ){
gameOver = true;
}
}
screen.flip();
}
}
SDL_KillThread(thread1);
SDL_KillThread(thread2);
return 0;
}
这段程序看起来似乎没有什么问题,但是运行的时候,不可预知的情况出现了:理论上我们几乎同时调用了两个线程,动画似乎应该是同步播放的,但是实际上,两段动画的播放并不同步,并且每次执行的效果都不一样——有时候上面的图片移动快,有时候下面的图片移动快,并且速度不均匀。
这就是典型的race conditions的表现。还记得我说过没有定义dt吗,我们让电脑以其所能达到的最快速度决定dt,换句话说,我们每一个线程都试图“咬死”CPU的运算,当然,在实际中多任务的OS会帮助CPU分配任务,但是如何分配却是不确定的,因为OS并不知道哪些任务需要优先执行,所以,两个线程实际上在竞争电脑的性能资源,产生的结果就是不确定的。
2.2:松开“死咬”的CPU
void SDL_Delay(Uint32 ms);
解决race conditions的方法就是给CPU足够的时间“休息”,而这正好也是我们自己定义dt所需要的。SDL_Delay()在这个时候就显得意义重大了。当今电脑的运算速度非常非常快,以至于哪怕我们仅仅给电脑0.01秒的时间“休息”(每次循环中),电脑都会显得很轻松了。
int amn(void* data)
{
AmnArg* pData = (AmnArg*)data;
PictureSurface stand("./images/am01.png", pData->screen);
stand.colorKey();
PictureSurface bg("./images/background.png", pData->screen);
const int SPEED_CTRL = 300;
int speedX = (pData->endX - pData->beginX) / SPEED_CTRL;
int speedY = (pData->endY - pData->beginY) / SPEED_CTRL;
for ( int i = 0; i < SPEED_CTRL; i++ ){
pData->beginX += speedX;
pData->beginY += speedY;
bg.blit(pData->beginX, pData->beginY, pData->beginX, pData->beginY, stand.point()->w, stand.point()->h, 2, 2);
stand.blit(pData->beginX, pData->beginY);
pData->screen.flip();
SDL_Delay(10);
}
return 0;
}
说到这里,我们不得不提及之前一直所忽略的一个问题:我们之前凡是涉及循环等待事件轮询的程序总是占用100%的CPU,这并不是因为我们真正用到了100%的CPU性能,而是我们让CPU陷入了“空等”(Busy Waiting)的尴尬境地。轮询事件得到响应相对于循环等待来说,是发生得非常缓慢的事情,我们在循环中,哪怕是让电脑休息0.01秒,事情都会发生本质性的改变:
while ( gameOver == false ){
while ( SDL_PollEvent(&gameEvent) != 0 ){
if ( gameEvent.type == SDL_QUIT ){
gameOver = true;
}
if ( gameEvent.type == SDL_KEYDOWN ){
if ( gameEvent.key.keysym.sym == SDLK_ESCAPE ){
gameOver = true;
}
}
screen.flip();
}
SDL_Delay(10);
}
当我们重新运行新程序的时候,我们可以看到程序对CPU的占用从100%骤降到了0%!这当然并不意味着程序就用不上CPU了,而是说,这些运算对于我们的CPU来说,实在是小菜一碟了,或者从数据上说,处理这些运算的时间与0.01秒来比较,都几乎可以忽略不计!
2.3:GUI线程与worker线程
我们的另外一项试验是将事件轮询放到动画线程中,程序就不多写了,大家可以自己试下。我直接说结论:动画线程中无法响应事件轮询。
一般提倡的模式,是将GUI事件都写在主线程中,而将纯粹的运算才写到由主线程创建的线程中,后者也就是所谓的worker线程。从另外一个概念看,只有主线程控制着“当前窗口”,其它线程也许在后台,也许虽然也是在前台但是并非是我们可见的,所以,轮询事件找不到接口。
对于抛出的线程与主线程之间的通讯,我们可以通过他们共享的数据来进行控制,所以,尽管事件轮询不能直接影响worker线程,但是我们仍然是可以通过主线程进行间接影响的。
|
|