2011年06月10日

排他制御の二重呼び出しでデッドロックを起こすときどうします?

例えば、VRAMのようなシステムに一つしかないリソースを複数のタスクからアクセスするライブラリAPIを作るときに、提供するAPIの内部でセマフォやミューテックスを使って排他制御をするとします。

以下のようなコードになったとします。



void funcA( void )
{
mutex.lock();

funcB();

mutex.unlock();
}



このとき、funcB()の内部からfuncA()を呼び出すと、当然 mutex.lock() の箇所でデッドロックを起こします。



void funcB( void )
{
funcA();
}



このケースは、論外とは言いませんが、もう少し設計を考えて funcA() を呼び出さないようにすれば回避できそうです。

では funcB()内部から、funcA()を呼び出したアプリケーション側にイベント通知の為にコールバック関数を呼び出し、呼び出されたコールバック関数から funcA() が呼び出されたらどうでしょうか?
(意外と、アプリケーション側とライブラリ側の担当者が別々だったりすると、こんなことはよく起こります。)



void funcB( void )
{
app->callbackA();
}

void callbackA()
{
funcA();
}



これも、アプリケーション側の設計をもう少し考えて。。。なんていうのが通用しないことがあります。
(ある程度プログラムの作り込みが完了しているところに、アプリケーション側の担当者にプログラム変更を依頼すると、「大幅な変更になるので対応できません。」と拒否されたりします。
まぁ、この辺りは個々の担当者間の力関係で決まってきます。。。もうプログラミングとは関係ない話です。)

なんとかライブラリAPI内で解決したいところです。

私なら安直に以下のように修正します。
(まぁ、別によくある手法(?)です。)



void funcA( void )
{
static int locked_task_id;

int lock_flg = 0;
int task_id = get_task_id();

if( locked_task_id != task_id ){
mutex.lock();
locked_task_id = task_id;
lock_flg = 1;
}
funcB();

if( lock_flg == 1 ){
locked_task_id = 0;
mutex.unlock();
}
}



そもそも、ここで問題になっているのは、同一のタスクから二重に mutex.lock() を呼び出している事なので、mutex.lock()を呼び出したタスクのタスクIDを保持しておきfuncA()が呼び出されたときに、mutex.lock()を掛けたタスクと同一のタスクIDの場合、mutex.lock() を呼び出さないようにします。
当然、mutex.lock()を呼び出さなかったときは mutex.unlock() も呼び出さないようにします。

何となくうまく行きそうですがどうでしょうか?

タスクIDが取得出来ないようなOSの場合。。。。どうしましょう(;^_^A


posted by kana-soft at 02:07| Comment(2) | ソフトウェア
この記事へのコメント
例えばPOSIXの場合、
下記の属性を指定してMutexを作成しておけば、
多重に排他をかけても、LockとUnlockがセットになっていれば問題なく動くはずですよ。

PTHREAD_MUTEX_RECURSIVE_NP

ただ、上記のfuncA()、funcB()は無限ループのような気がします w
Posted by oku3ma at 2011年11月21日 10:30
ご指摘ありがとうございます。

POSIXの件、知りませんでした。

ついでにWindowsのMutexも再帰ではロックしないようで。。。

サンプルも無限ループしてますね。。。
(近い内に直します。)
Posted by 作者 at 2012年01月11日 00:14
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: