NSThread/NSAutoreleasePool

マルチスレッドについての四方山話


Cocoaはマルチスレッドプログラムが非常に作りやすい仕組みを提供してくれています。せっかくの機能なのでちょいと使い込んでWiggleというソフトとそれ用のプラグイン("第8号 勝手にPlugin"に掲載)を作ってみました。ここではWiggleの製作を通してわかったこととか、なんだか良くわからない現象が発生したことについてのお話です。
※掲示板のスレッドとは違います念のため・・


<= 探検レポートトップに戻る

マルチスレッドってなにさ・・
スレッドは作らなきゃ動かない
無限ループを作る

マルチスレッドの四方山話(続き)に続く=>


【マルチスレッドってなにさ・・】

コンピュータはCPUが読み込んだ命令を順番に処理を進めます。この処理の流れを時間軸とします。
通常のアプリケーションはこの時間軸が1本しかありません。
マルチスレッドアプリケーションはこの時間軸を複数持つプログラムのことをいいます。

例えば、iTunesのようなソフトを考えてみましょう。
iTunesはCDをリッピングしながらも音楽ライブラリの再生が可能です。更に、ライブラリーの編集やメニュー操作までもできますよね。
これをシングルスレッドでプログラムを組もうとすると、音楽の再生ルーチンやCDのリッピングルーチンの中でもOSのイベントチェックとその処理を行わなければならなくなるので非常にややこしい構造になってしまうことがわかるかと思います。

時間軸を複数持ったマルチスレッドの場合は、音楽再生専用ルーチンとCDのリッピング専用ルーチンをそれぞれスレッド化することで、これらのルーチンが同時進行するプログラムが実現できてしまうのです。

このように、マルチスレッド化は一般にいわれているようにCPUの有効利用の他に複雑な動作をするプログラムの実行ルーチンを単純化する強力な武器にもなります。

※一般にマルチスレッドは複数の時間軸が持てると書きましたが、これは人間の時間感覚で見た場合の表現です。CPUレベルの時間で見た場合1つの時間軸上で複数のスレッドが切り替えながら処理されています。
しかし、このややこしい部分はOSやフレームワークがサポートしてくれるので意識する必要はありません。またこれらの処理は時間の切換えがあまりにも早いので人間の感覚では同時進行していると考えても差し支えないということになります。

Topに戻る↑


【スレッドは作らなきゃ動かない】

こんな便利なスレッドだけれど作り方は非常に簡単です。スレッド化そのものは定型パターンがあるので呪文のようにそのパターンを使えば簡単に実現できます。
リスト1のdoMyThread:argumentData:がスレッドを実行するメソッドです。
引数の有無2種類のメソッドを記載したので長くなっていますが基本的には次の1行でスレッドが作れてしまいます。

+(void)detachNewThreadSelector: toTarget:withObject:

doMyThreadは、detachNewThreadSelectorメソッドを実行すると直ぐに終了し、メインイベントループに戻ります。

リスト1

-(void)doMyThread:(int)actionIndex argumentData:(double)argument
  {
   switch(actionIndex)
    {
     case 0:
      {
       [NSThread detachNewThreadSelector:@selector(action1)
        toTarget:self withObject:nil];
       break;
      }
     case 1:
      {
       [NSThread detachNewThreadSelector:@selector(action2:)
        toTarget:self withObject:[NSNumber numberWithDouble:argument]];
       break; 
      }
    }
  }

続いて、スレッドの実行部分はリスト2のようになります。
ここでも引数の有無2種類のメソッドがありますがスレッドルーチンの先頭とお尻には同じ呪文を使います。

★スレッドの呪文★

1) メソッド実行部の先頭でNSAutoreleasePoolを作ります。
2)続いて実行部を実装します。
3)スレッド終了時にNSAutoreleasePoolを削除し、最後にNSThreadのexitメソッドで終了します。
たったこれだけで終り・・

リスト2

-(void)action1
  {
    NSAutoreleasePool* pool;
    pool = [[NSAutoreleasePool alloc]init];
    *********************************
    ****  ここにスレッドの動作を実装
    *********************************
    [pool release];
    [NSThread exit];
  }

-(void)action2:(id)data
  {
    NSAutoreleasePool* pool;
    pool = [[NSAutoreleasePool alloc]init];
    *********************************
    **** ここにスレッドの動作を実装
    *********************************
    [pool release];
    [NSThread exit]; 
  }

Topに戻る↑


【無限ループを作る】

スレッドの構造がわかったところで、もう少し具体的に無限ループを持つスレッドの構造を紹介します。
細かい実装は省略しているので適当に追加してください。

リスト3A(無限ループを持つスレッドのクラス宣言)

@interface mugenSample : someObject
  {
    BOOL mugenFlag;
  }
 -(void)runMugen;      // スレッドを開始する
 -(void)stopMugen;     // スレッドを停止する
 -(void)mugenThread;   // 無限ループ実行ルーチン
@end

mugenSampleクラスに1個のインスタンス変数と、3個のインスタンスメソッドを実装します。
インスタンス変数mugenFlagは無限ループを終了させるフラグになります。

リスト3B(無限ループを持つスレッドのメソッドの実装)

-(void)runMugen
  {
    if(mugenFlag)
      return;

    mugenFlag = TRUE;
    [NSThread detachNewThreadSelector:@selector(mugenThread)
     toTarget:self withObject:nil];  
  }

-(void)stopMugen 
  {
     mugenFlag = FALSE; 
  }

-(void)mugenThread
  {
    NSAutoreleasePool* pool;
    NSAutoreleasePool* internalPool; 

    pool = [[NSAutoreleasePool alloc]init];
    while(mugenFlag)
     {
       internalPool = [[NSAutoreleasePool alloc]init];
        *********************************
        **** ここにスレッドの動作を実装
        *********************************
       [internalPool release]; 
     }
   [pool release];
   [NSThread exit];
 } 

-(void)runMugen
無限ループを実行するインターフェースです。ここでmugenFlagをチェックし、TRUEの場合は無限ループが既に実行中なので何もせずに戻ります。FALSEであれば、mugenFlagにTRUEをセットし、NSThreadを使って実行ルーチンmugenThreadを動かします。

-(void)stopMugen
無限ループを停止するためにmugenFlagにFALSEをセットします。

-(void)mugenThread
無限ループの実行ルーチンです。runMugenから呼び出されます。
NSAutoreleasePoolのおまじないを宣言してmugenFlag がFALSEになるまで無限ループを実行します。
ここでは、無限ループの中でもNSAutoreleasePoolを作っています。
この実装により、スレッド内で発生したリリースオブジェクト予備軍は無限ループ1回ごとに一括削除されるのでメモリーの利用効率が改善します。



(C) 2007 Cocoa探検隊