2011-09-15 10 views
2

私はマルチスレッドの状況にあり、一度に1つのスレッドからしか実行したくない機能があります。しかし、伝統的な方法で関数をシリアライズするのではなく、最初のスレッドがそれを実行している間に関数を入力しようとするスレッドがすぐに戻るようにしたい。私は2番目のスレッドが最初のスレッドを待つことを望んでいません。私はスレッドを持つ試験サンプルを行うことができなかった原因ミューテックスとスレッドに関する問題

function InitMutex(const Name:String; var Handle: THandle):Boolean; 
begin 
Handle := CreateMutexA(NIL, True, PAnsiChar(Name)); 
Result := not (GetLastError = ERROR_ALREADY_EXISTS); 
end; 


procedure TForm1.Button1Click(Sender: TObject); 
var 
mHandle: THandle; 
begin 
if not InitMutex(BalloonTipMutex, mHandle) then Exit; 


MessageBox(0, 'Executing Code....', '', 0); 


ReleaseMutex(mHandle); 
CloseHandle(mHandle); 
end; 

これは、同じ問題を持つ単なる例です。

は、ここに私のコードです。

問題は次のとおりです。最初にbutton1をクリックします。メッセージボックスが表示されている間にメッセージボックスが表示されている間(関数がまだ動作していると仮定します)再度button1を押しても何も表示されませんメッセージボックスを閉じてもう一度ボタンを押すと、何も表示されません。 (そのが実行されていないため、再実行することになっ機能:S)

答えて

3

が代わりにこれを試してみてください:

procedure TForm1.Button1Click(Sender: TObject); 
var mHandle: THandle; 
begin 
    mHandle := 0; 
    if InitMutex(BalloonTipMutex, mHandle) then 
    begin  
    MessageBox(0, 'Executing Code....', '', 0); 
    ReleaseMutex(mHandle); 
    end; 
    if handle <> 0 then 
    CloseHandle(mHandle); 
end; 

あなたの問題は、CreateMutexがエラーERROR_ALREADY_EXISTSを返したとしても、それはミューテックスを「開く」でした...です。最初の関数が終了すると、2回目の呼び出しが開かれてからmutexは解放されませんが、決して閉じません。だから、あなたの関数を3回目に呼び出そうとすると、あなたの最初の呼び出しがmutexを開いたままにしたのではなく、2回目の呼び出しが失敗したからです。

また、私はInitMutexがResult := (Handle <> 0) and not (GetLastError = ERROR_ALREADY_EXISTS)

EDITを返すべきだと思う:サイドノートでは、これは本当にミューテックスが使用されることを意図されている方法ではありません。 mutexを使用する "伝統的な"方法は、mutexを作成し、mutexで保護されたコードを実行するときにスレッドの所有権を取得させることです。 CreateMutexはミューテックスの所有権を取るよりも少し遅いと思っていますが、そのテクニックには他にもいくつかの落とし穴があります。

+0

ありがとう、それは仕事を完全にしました:D、私はこの状況で使うべきだと思いますか? – killercode

+0

このため、ミューテックスは非常に重いです。これが最良のソリューションだとは考えにくいです。 –

+0

@Killercode、これについて別の質問をすることをお勧めします。 –

3

私は最終的にこの問題を理解したので、最も効率的な解決策はインターロックされた操作を使用することです。

procedure OneAtATimeThroughHere; 
//FLockCount is a properly aligned integer, shared between all threads 
var 
    ThisLockCount: Integer; 
begin 
    ThisLockCount := InterlockedIncrement(FLockCount); 
    try 
    if ThisLockCount=1 then//we won the race 
    begin 
     //do stuff 
    end; 
    finally 
    InterlockedDecrement(FLockCount); 
    end; 
end; 

このアプローチでは、リエントラントコールは許可されません。リエントラントコールを処理する必要がある場合は、解決方法はTryEnterCriticalSection()です。クリティカルセクションはミューテックスよりもはるかに使いやすく、高速です。 Delphiは、SyncObjsユニットのTCriticalSectionオブジェクトにクリティカルセクションAPIをラップします。

だからあなたのコードは次のようになります。

procedure OneAtATimeThroughHere; 
//FLock is an instance of TCriticalSection shared between all threads 
if FLock.TryEnter then 
begin 
    try 
    //do stuff 
    finally 
    FLock.Release; 
    end; 
end; 
+0

連動したアプローチについては、おそらく 'InterlockedCompareExchange'を代わりに使用します。あなたのアプローチには、そうでなければコールを失うチャンスはありますが、重要ではありません。 –

+0

@kenいいえ、それは電話を失うことはありません –

+0

まあ、AFAIK、この例が可能です:スレッド1関数を入力します(所有権を取る)。スレッド2が入り、インクリメントカウンタだけが入ります。 Thread1は関数を終了します。スレッド3は(スレッド2が終了する前に)関数に入ります。関数が "所有"されていないにもかかわらず、Thread2はまだその関数の所有権を(偽)所有しているため、Thread3は所有権を取得できません。 –

3

を代替ソリューションとして、あなたはAddAtom()FindAtom()DeleteAtom() Windows API関数を使用することができます(参照:http://msdn.microsoft.com/en-us/library/ms649056(v=vs.85).aspxを)。プロセス間で使用するためのグローバルバージョンもあります。

アトムを使用すると、スレッドの流れを完全に制御し、関数内にロック機構全体を含めることができます(クリティカルセクションの場合のように)。

+0

+1これは正常に動作します。しかし、FindAtom()の必要はありません。追加と削除だけです。 –

1

ミューテックスを一度作成し、スレッドが実行されている間はそれを保持してから、0から255ミリ秒のタイムアウトで関数を使用してミューテックスロックを取得する必要があります。WaitForSingleObject()WAIT_OBJECT_0を返した場合、関数はまだ実行されていません。

var 
    mHandle: THandle = 0; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    mHandle := CreateMutex(nil, False, nil); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    CloseHandle(mHandle); 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    if WaitForSingleObject(mHandle, 0) = WAIT_OBJECT_0 then 
    begin 
    try 
     MessageBox(0, 'Executing Code....', '', 0); 
    finally 
     ReleaseMutex(mHandle); 
    end; 
    end; 
end; 
関連する問題