Nanashi-softプログラマ専用Windows gcc SDL


◇Windowsでgcc+SDL -スレッド処理改-

前回、画面下半分をスレッド化すると点がボロボロになりました
その原因と対処を行います

○問題原因の推測

点がボロボロになる原因は何か?
表示後のウィンドウを最小化して、開き直すときちんと表示されます
つまり、点描画はうまくいっているが、表示する際にうまくいっていない事がわかります

同時に表示しようとすると不具合が起こる。と推測してみます
SDL_UpdateRectを同時に2つ実行すると衝突するのではないか? と言う事です
そこで、同時に処理しないようにミューテックスを用いて排他処理してみます

○複数の引数の渡し方

ここで問題が発生します
実画面のサーフェスとミューテックスの2つのポインタを渡さなければなりません
スレッドへ渡せるのは1つのポインタのみです
どうするのかと言うと、構造体を定義して渡します

//スレッドに引き渡す構造体定義
struct thread1Data{
  SDL_Surface *screen;
  SDL_mutex *mtx;
};

メインスレッドでミューテックスを生成して、スレッド生成時に渡します

  //ミューテックス作成
  SDL_mutex *mtx;
  mtx= SDL_CreateMutex();

  //スレッドに渡す値をセット
  struct thread1Data data;
  data.screen = screen;
  data.mtx = mtx;

  SDL_Thread *pt1;
  pt1 = SDL_CreateThread(thread1, (void *)&data); //スレッドを実行

スレッド側でこれを受け取ります

int thread1(void* args){
  struct thread1Data *data=(struct thread1Data*)args;

○ミューテックスを使う

表示する際に、ミューテックスをロックします

      SDL_mutexP(mtx); //ミューテックスをロック
      //点を表示
      SDL_UpdateRect(screen,x,y,1,1);
      SDL_mutexV(mtx); //ミューテックスをアンロック

これをスレッド側でも行います

      SDL_mutexP(data->mtx); //ミューテックスをロック
      //点を表示
      SDL_UpdateRect(data->screen,x,y,1,1);
      SDL_mutexV(data->mtx); //ミューテックスをアンロック

こうする事で、同時にSDL_UpdateRectが実行される事は無くなります

○結果

これを実行すると、きちんと描画されました
しかし、処理時間は6.4秒かかりました
シングル状態で3.7秒だった事を考えると、このスレッド化は全くの無意味という事になります(>_<)"

この例では、設計段階からスレッド化する事を考えてプログラミングを行わなければ、スレッド化する事は不可能と言うことを示しています
マルチコアCPUが当たり前になった今では、こんなプログラムを書くプログラマはダメと言うことです('-'*)

○ソース

#include "SDL.h"

//スレッドに引き渡す構造体定義
struct thread1Data{
  SDL_Surface *screen;
  SDL_mutex *mtx;
};

int thread1(void* args){
  struct thread1Data *data=(struct thread1Data*)args;

  int x,y;
  for(y=240; y < 480; y++){
    for(x=0; x < 640; x++){
      SDL_Rect box;
      box.x = x;
      box.y = y;
      box.w = 1;
      box.h = 1;
      //点を描画
      SDL_FillRect(data->screen, &box, SDL_MapRGB(data->screen->format,0,0xff,0));
      SDL_mutexP(data->mtx); //ミューテックスをロック
      //点を表示
      SDL_UpdateRect(data->screen,x,y,1,1);
      SDL_mutexV(data->mtx); //ミューテックスをアンロック
    }
  }
}

int main(int argc,char *argv[]){
  int done;
  SDL_Event event;

  if(SDL_Init(SDL_INIT_VIDEO) < 0){
    return -1;
  }

  SDL_Surface *screen=SDL_SetVideoMode(640,480,32,SDL_SWSURFACE);;
  if(screen == NULL){
    SDL_Quit();
    return -1;
  }

  Uint32 t1= SDL_GetTicks();

  //ミューテックス作成
  SDL_mutex *mtx;
  mtx= SDL_CreateMutex();

  //スレッドに渡す値をセット
  struct thread1Data data;
  data.screen = screen;
  data.mtx = mtx;

  SDL_Thread *pt1;
  pt1 = SDL_CreateThread(thread1, (void *)&data); //スレッドを実行

  int x,y;
  for(y=0; y < 240; y++){
    for(x=0; x < 640; x++){
      SDL_Rect box;
      box.x = x;
      box.y = y;
      box.w = 1;
      box.h = 1;
      //点を描画
      SDL_FillRect(screen, &box, SDL_MapRGB(screen->format,0xff,0,0));
      SDL_mutexP(mtx); //ミューテックスをロック
      //点を表示
      SDL_UpdateRect(screen,x,y,1,1);
      SDL_mutexV(mtx); //ミューテックスをアンロック
    }
  }

  SDL_WaitThread(pt1, NULL); //スレッド処理終了を待つ

  SDL_DestroyMutex(mtx); //ミューテックスを破棄

  printf("time=%d\n", SDL_GetTicks() - t1);

  done = 0;
  while(!done){
    while(SDL_PollEvent(&event)){
      switch(event.type){
      case SDL_KEYDOWN:
        if(event.key.keysym.sym == SDLK_ESCAPE){
          done = 1;
        }
        break;
      case SDL_QUIT:
        done = 1;
        break;
      }
    }
  }

  SDL_Quit();

  return 0;
}



TOPプログラマ専用Windows gcc SDL