[ Home | Programming Tips | Mail ]

LThread class Usage 1


LThread class活用法 その1

 現在の MacOSの Thread Managerで提供される Threadは Pre-emptive Threadではありませんので、適宜 Yieldで明示的に Threaed Switchしないといけません。
 それでも Threadを利用すると Programの設計や見通しが簡単になる場合が多いですし、User Interfaceに関係無い部分を別 Threadに分離して Event Loopを軽くすることで、スムーズな使用感を持った Applicationを作ることができます。

 ちなみに PowerMac上の Thread Switchは非常に高速ですので、多数の Threadを数 msec間隔で頻繁に切り換えても殆ど負荷はかかりません。PowerMac8100/80という今となっては最も遅い部類の PowerMacで Yieldするだけの Threadを作って動かすと 10m secの間に1000回程度 Thread Switchします。
 今の標準的な PowerMacでしたら数μ sec程度の Overheadで Thread Switchできるでしょう。

 Threaded Applicationでは、Main Threadが User Interface Handlingと Thread Schedulingを行いますが、以下の点に気をつけます。

  1. LThread::CountReadyThreads()で Ready Threadの有無を判断する。
  2. Ready Threadがある時は Time Sliceで CPU時間を与え続ける。
    Process Sleep Tickも短くする。
  3. Ready Threadがない時はさっさと Processを Suspendさせる。
    Process Sleep Tickも長くて良い。
  4. Main Threadは User Interface Handlingだけで軽くし、実作業は Main Thread以外で行う。Main Threadは Sleepしたり Blockされてはいけない。
 また Main Thread以外では、
  1. やることが無くなったらさっさと Suspend / Sleepするか Que / Semaphore待ちにして CPU使用権を放棄する。
  2. Blocking I/Oを使い I/O待ち時間は他の Threadに使わせる。
  3. 2〜3m secに1回ぐらい Yieldすれば十分なので、Loop内等では Yieldし過ぎで効率を落とさない様にする。
と言った点に注意します。

 PowerPlantの LThread class familyは Shared Que / Semaphore / Timer / Blocking I/Oといった Concurrent Programmingに必要な機能を提供してくれています。
 簡単な例として、2秒間隔で Scheduleされる Threadで Beepを鳴らす Threaded PowerPlant Applicationの Project Fileをこちらに置いておきます。

MinPP_Thread.hqx

 PowerPlantには Thread Scheduling用の LYieldAttachment()が添付されていますが、これははっきり言ってスカですので使わない方が良いでしょう。
 Threaded PowerPlant Applicationの場合は LApplication::ProcessNextEvent()を以下の様に Overrideします。これで Threadに十分 CPU時間が配分されかつ User Interfaceもスムーズに処理できます。


void    CDashboardApp::ProcessNextEvent()
{
    EventRecord     macEvent;
 
    // When on duty (application is in the foreground), adjust the
    // cursor shape before waiting for the next event. Except for the
    // very first time, this is the same as adjusting the cursor
    // after every event.
    if (IsOnDuty()) {
        // Calling OSEventAvail with a zero event mask will always
        // pass back a null event. However, it fills the EventRecord
        // with the information we need to set the cursor shape--
        // the mouse location in global coordinates and the state
        // of the modifier keys.
        ::OSEventAvail(0, &macEvent);
        AdjustCursor(macEvent);
    }
    
    // Retrieve the next event. Context switch could happen here.
    SetUpdateCommandStatus(false);
 
    //-----------------------------------------------------------------------
    //  Ready Threadの有無で Process Sleep Timeを調節
    //-----------------------------------------------------------------------
    mSleepTime = (0 < LThread::CountReadyThreads()) ? 0 : LMGetCaretTime();
    Boolean gotEvent = ::WaitNextEvent(everyEvent,&macEvent,mSleepTime,mMouseRgnH);
    //-----------------------------------------------------------------------
    //  Sleepしていた threadが WakeUpされた場合なるべく早く Scheduleするため
    //-----------------------------------------------------------------------
    if ( !gotEvent )  LThread::Yield();
    //-----------------------------------------------------------------------
 
    // Let Attachments process the event. Continue with normal
    // event dispatching unless suppressed by an Attachment.
    if (LAttachable::ExecuteAttachments(msg_Event,&macEvent)) {
        if (gotEvent) {
            DispatchEvent(macEvent);
        }else{
            UseIdleTime(macEvent);
        }
    }
                                    // Repeaters get time after every event
    LPeriodical::DevoteTimeToRepeaters(macEvent);
                                    // Update status of menu items
    if (IsOnDuty() && GetUpdateCommandStatus()) {
        UpdateMenus();
    }
    
    //-----------------------------------------------------------------------
    //  Ready threadがあれば一定時間 scheduleする
    //  Foreground / Backgroundで Time Sliceを変えたりとかさらに工夫も可能
    //-----------------------------------------------------------------------
    const Uint32    kLimitTick = LMGetTicks() + 2;
    const QHdr      *qEvt      = LMGetEventQueue();
    do {
        LThread::Yield();
    }while (   (qEvt->qHead == nil)
            && (0 < LThread::CountReadyThreads())
            && (LMGetTicks() < kLimitTick));
    //-----------------------------------------------------------------------
}


この Pageは MacOS X + Radio UserLand で作っています。