BREW で MicroThread(3)

晒されてたので現れてみるテスト
http://pc11.2ch.net/test/read.cgi/tech/1195449992/

416 :403  [sage] :2008/04/11(金) 04:33:47
    >>406,407,409,410,411,412,413
    おまいらありがとう。
    静的な変数はELF2MODでいけるってことね。
    例外はRVCT3.0で>>9みたいに__cxa〜とmalloc類を実装し直せばいけるってことね。
    スレッドはマイクロスレッドか〜。
    melponさんの日記で見たけどローカル変数の解放処理が上手くいってないみたいのは解決できたのかな?
    http://d.hatena.ne.jp/melpon/searchdiary?word=MicroThread%20
    例外が使えれば〜みたいなこと言ってるけど何のことか分からん、上の例外の話で解決出来るんかな。
    もしくは他に誰かマイクロスレッド実装した人いるのかな?

417 :デフォルトの名無しさん [sage] :2008/04/11(金) 10:48:22
    IThreadなら実装試したことなら有る。
    melpon氏と同様saspend中のローカル変数のデストラクタが呼ばれない問題があったんで
    ローカル変数にクラスを使わないってマゾルールで。

    IThreadはそもそもBREW3.0未満では使用不可だったりしたし
    アプリ起動中のcpuパワーが殆どスレッド処理に持ってかれて、
    更にsuspend中もcpuパワー奪いっぱなしで使いモンになんなかった。

    melpon氏のマイクロスレッドはタスク切り替えは問題なさそうだけど
    BREW端末って1秒くらい処理占有すると勝手に落ちたのではと。。。
    その辺解決してるのかな。

ちなみに自分はもう BREW から離れて久しく、MicroThread も未完成のままですので、以下の方法は全部草案というか妄想というか、そんな感じです。
あと、MicroThread クラスのインターフェースは今考えるとあまり良くないなぁとか思ってたりするので、ほんとに MicroThread を完成させたい方は Boost.Coroutine とかを見て、容量と戦いながら最適なインターフェースを見つけ出して頂ければと思います。



まず、そもそもなぜローカル変数が解放されないかということですが、関数を途中で停止させている際に MicroThread が破棄される可能性があるからです。

class Hoge : public MicroThread
{
public:
    void OnMove()
    {
        if (!isExecute())
        {
            start();
        }
        else
        {
            resume();
        }
    }
    
    virtual void run()
    {
        Fuga fuga;
        int x = 0;
        for (int i = 0; i < 100; i++)
        {
            x += 2;
            fuga.foo();
            suspend();
        }
    }
};
Hoge* pHoge = new Hoge();
pHoge->OnMove(); // x == 2
pHoge->OnMove(); // x == 4
delete pHoge;

run() を抜けると Fuga のデストラクタが呼ばれます。
しかし、suspend() している間にアプリケーションが終了したり Hoge クラスが破棄されたりすると、Fuga クラスのデストラクタは呼ばれません。
ここが問題なわけですね。


これを解決するためにはいくつか方法があると思います。
自分の思いつく方法は、以下の3つです。

  1. ローカル変数で解放(デストラクタ)が必要になる処理を使わない
  2. 終了通知を戻り値で最上位(run 関数)まで渡す
  3. 終了通知を例外で最上位まで渡す


1はローカル変数を全く使わず、全てメンバにしてしまう方法です。
こうすれば確かに途中で破棄しても問題ないのですが、さすがにそうやって書くのは厳しいものがあります。


2の方法はなかなか良さそうな気がします。
suspend() 関数に戻り値を付けて、破棄を要求された場合は true を返すことにしましょう。

    virtual void run()
    {
        Fuga fuga;
        int x = 0;
        for (int i = 0; i < 100; i++)
        {
            x += 2;
            fuga.foo();
            if (suspend()) return;
        }
    }

これで OK です。
あとは MicroThread のデストラクタで、

MicroThread::~MicroThread()
{
    m_bEnd = true;
    while (isExecute()) resume();
}

こんな感じで待てば、run() 関数は正常に抜けているのでローカル変数のデストラクタは無事呼ばれます。


欠点としては、suspend() で return することを忘れてはいけないということです。
もし忘れたらいろいろと良くないことが起こるに違いありません。


3の方法ですが、これは「例外が発生してもローカル変数のデストラクタは呼ばれる」ということを利用します。
つまり、中断させたくなったら suspend() 中に例外を発生させてやるのです。
suspend() で MicroThread のコンテキストに切り替わる際に、2の方法では bool を返していましたが、その bool の値が true だった場合は例外を投げることにします。

void MicroThread::suspend()
{
    // suspend_ は2の実装で使った suspend だと思ってくらはい
    if (this->suspend_()) throw micro_thread_interrupted_exception();
    return;
}

こうすると、suspend した際に戻り値をチェックする必要が無くなります。これが一番の理想です。


ただし、例外機構の内部実装は環境毎に違うので、コンテキストを切り替えていても正常に例外が投げられるようにするには、コンパイラ依存な方法を使うことになるかと思います。
やね本2では Windows で MicroThread を実装するために、Win32 SEH(構造化例外処理)について調べて実装していました。
同様のことを RVCT3.0 で出来るのであれば、例外によってデストラクタを呼んでもらうことが出来るかもしれません。

BREW端末って1秒くらい処理占有すると勝手に落ちたのではと。。。

IThread が yield している間も CPU を使うというのが相当不思議なのですがそれはいいとして、私の想定している MicroThread の使い方は、ものすごく時間の掛かる処理を MicroThread に持って行き、メインのタイマで処理して貰うという感じなので、

// micro thread 側
void OnMove()
{
    if (!isExecute())
        start();
    else
        resume();
}

virtual void run()
{
    for (int i = 0; i < 100; i++)
    {
        // 時間の掛かる処理
        Sleep(100);

        suspend();
    }
}
// 呼び出し側
// BREW タイマのコールバックを使う
void Callback()
{
    m_microThread->OnMove();

    // 次のタイマの登録
}

こんな感じになります。
時間の掛かる処理を1回やると suspend されるので、その時点で処理が戻ってきます。
で、一度システムの方に返して、またタイマで続きの処理をしてもらいます。
こうすることで、ほんとなら Sleep(100) * 100 → 10000ms 掛かっていた処理を、ロジックをほとんど変更することなく実装することが出来ます。



そういえば忘れていたのですが、後一つ方法があります。
処理の流れを自前で解析して、状態機械に置き換えてしまうことです。
例えば C#2.0 以降に搭載された yield 機構なんかはそれをやっているので、自分で yield 使ったプログラムを書いて Reflector か何かで覗いてみると、状態機械に置き換えている様が見られると思います。
もしこれが実装できる能力があれば、それが一番な気がしますね。自分には無理ですが。




あともう一つだけ。
new した領域があるときに suspend をして、そこで MicroThread が終了した場合、依然として領域は開放されません。
これはちゃんと shared_ptr なり何なりのスマートポインタを使おうと言うことです。

2009/06/27 追記

Boost.Coroutine を BREW で使えるようにしてみました。

これは例外を有効にする必要がありますが……。