[ Home | Programming Tips | Mail ]
現在の 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を行いますが、以下の点に気をつけます。
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 で作っています。