- PR -

mutex の動作について

1
投稿者投稿内容
やまと
会議室デビュー日: 2003/08/21
投稿数: 11
投稿日時: 2004-05-14 11:29
お教え下さい。

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

コード:
Imports System.Threading
Public Class MutexTest
    Private Shared m_MutexTest As MutexTest

    Public Shared Sub Main()
        m_MutexTest = New MutexTest
        Application.Run()
    End Sub

    Private Sub New()
        Threading.ThreadPool.QueueUserWorkItem(AddressOf th1, Nothing)
        Threading.ThreadPool.QueueUserWorkItem(AddressOf th2, Nothing)
    End Sub

    Private Sub th1(ByVal state As Object)
        Dim m As New Mutex(False, "TEST_MUTEX")
        Dim startTime As DateTime = DateTime.Now
        m.WaitOne()
        Thread.Sleep(1000)
        Console.WriteLine("th1_待機時間 : " & DateTime.Now.Subtract(startTime).TotalSeconds)
    End Sub
    Private Sub th2(ByVal state As Object)
        Dim m As New Mutex(False, "TEST_MUTEX")
        Dim startTime As DateTime = DateTime.Now
        m.WaitOne()
        Thread.Sleep(1000)
        Console.WriteLine("th2_待機時間 : " & DateTime.Now.Subtract(startTime).TotalSeconds)
    End Sub
End Class



投稿用に検証的な部分のみを記述したコードなので、
実際にはこのようなmutexを解放しないコードを記述することはありませんが、
このコードに予期している動作は、
コンストラクタによってth1 メソッドと th2 メソッドをスレッドプールに置き、
先に動作したスレッドの "th*_待機時間 : 秒数" が表示される、というものです。

MSDNの WaitHandle.WaitOne メソッド(引数無し)のヘルプには、
引用:
このメソッドの呼び出し元は、現在のインスタンスがシグナルを受け取るまでは、
無期限にブロックします。


とあるので、先に動作したスレッドが mutex を解放するコードを記述しない限り、
後に動作したスレッドは、waitOne部分で無期限にブロックされるはずです。

しかし、実際にこのコードを実行してみると、
40秒後に、後に動作したスレッドによって、
"th2_待機時間 : 41.509688"
と表示されてしまいます。

これはなぜこの様な動作になってしまうのでしょう?

なお、動作環境は、CLR バージョン 1.1.4322.573 で、
会社と自宅の WindowsServer2003 と Windows2000Professional の
両方で、同現象を確認しました。
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2004-05-14 11:33
引用:

やまとさんの書き込み (2004-05-14 11:29) より:
とあるので、先に動作したスレッドが mutex を解放するコードを記述しない限り、
後に動作したスレッドは、waitOne部分で無期限にブロックされるはずです。

しかし、実際にこのコードを実行してみると、
40秒後に、後に動作したスレッドによって、
"th2_待機時間 : 41.509688"
と表示されてしまいます。

これはなぜこの様な動作になってしまうのでしょう?


かならず決まったような時間ですか?
単純にFinalizeによって自動解放されたのでは?って気もしますけど…
甕星
ぬし
会議室デビュー日: 2003/03/07
投稿数: 1185
お住まい・勤務地: 湖の見える丘の上
投稿日時: 2004-05-14 12:45

引用:

MSDNの WaitHandle.WaitOne メソッド(引数無し)のヘルプには、
このメソッドの呼び出し元は、現在のインスタンスがシグナルを受け取るまでは、
無期限にブロックします。


とあるので、先に動作したスレッドが mutex を解放するコードを記述しない限り、
後に動作したスレッドは、waitOne部分で無期限にブロックされるはずです。
[/quote]

貴方の書いたコードだと、mutexをスタック変数にしていますよね。当然スコープを外れたあと、いずれガベージコレクタに回収されて、mutexも開放されるわけですよ。

引用:

しかし、実際にこのコードを実行してみると、
40秒後に、後に動作したスレッドによって、
"th2_待機時間 : 41.509688"
と表示されてしまいます。

これはなぜこの様な動作になってしまうのでしょう?



なので、たまたま40秒後にガベージコレクタが働いてmutexが開放されただけでは?

_________________
甕星 <mikahosi@abox9.so-net.ne.jp>
http://blogs.msmvp.jp/mikahosi/
やまと
会議室デビュー日: 2003/08/21
投稿数: 11
投稿日時: 2004-05-14 13:11
なちゃ様、甕星様ご回答頂きありがとうございます。

ご指摘の通り、例えば th1 スレッドの末尾にThread.Sleep メソッドを入れておくと、
指定した秒数が40秒に、さらに加算されました。

そこで、各メソッドにスタック変数としていた m を
インスタンス変数とするべく、以下のように書き換えました。
コード:
    Private m_MTX1 As Mutex
    Private m_MTX2 As Mutex

    Private Sub th1(ByVal state As Object)
        m_MTX1 = New Mutex(False, "TEST_MUTEX")
        Dim startTime As DateTime = DateTime.Now
        m_MTX1.WaitOne()
        Thread.Sleep(1000)
        Console.WriteLine("th1_待機時間 : " & DateTime.Now.Subtract(startTime).TotalSeconds)
    End Sub
    'th2も同様に書き換える



しかしながら、やはり、同様の問題が発生してしまいました。

メソッド内でインスタンス変数に対しmutexのインスタンスを代入しても、
そのメソッドの動作が終了しスコープから外れると、
インスタンス変数に対してもFinalizeが呼び出されてしまうのでしょうか?

どうかお教え下さい。
やまと
会議室デビュー日: 2003/08/21
投稿数: 11
投稿日時: 2004-05-14 14:15
上記の事を検証するために、以下のコードを書いてみました。

コード:
Imports System.Threading
Public Class ScopeTester
    Private Shared m_ScopeTester As ScopeTester
    Public Shared Sub Main()
        m_ScopeTester = New ScopeTester
        Application.Run()
    End Sub

    Private Sub New()
        Threading.ThreadPool.QueueUserWorkItem(AddressOf th1, Nothing)
        Threading.ThreadPool.QueueUserWorkItem(AddressOf th2, Nothing)
    End Sub

    Private m_tc1 As TestClass
    Private m_tc2 As TestClass

    Private Sub th1(ByVal state As Object)
        m_tc1 = New TestClass
    End Sub
    Private Sub th2(ByVal state As Object)
        m_tc2 = New TestClass
    End Sub
End Class

Public Class TestClass
    Public Sub New()
        Console.WriteLine(".ctor : " & Thread.CurrentThread.GetHashCode)
    End Sub

    Protected Overrides Sub Finalize()
        MyBase.Finalize()
        Console.WriteLine("Finalize : " & Thread.CurrentThread.GetHashCode)
    End Sub
End Class



上記コードを動作させると、各スレッドによるコンストラクタ呼び出しは
確認できるのですが、

40秒経過後、Finalize は呼ばれていないようなのです。

やはり、Mutex オブジェクトのみで起こる現象なのでしょうか?
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2004-05-15 21:21
Threading.ThreadPool.QueueUserWorkItem()ではなく、Thread.Start()でやってみたところ、40秒も待たずに片方のメソッドが終了するとすぐにリリースされました。

これはスレッド自身が、自分が所有しているMutexを(内部で)覚えていて終了するときにリリースしているのではないでしょうか。

QueueUserWorkItem()の場合はひとつのスレッドでキューに溜まっているメソッドを順番に呼び出すの(かな?)でしょうから、「40秒後にリリース」というのはThreadPoolクラスの機能かもしれませんね。

ちなみにGC.Collect()を呼んでみましたが、効果はありませんでした。
GCは関係ないみたいですね。
やまと
会議室デビュー日: 2003/08/21
投稿数: 11
投稿日時: 2004-05-15 23:02
一郎様、ご回答頂きありがとうございます。

私自身でもいろいろ試行錯誤してみたところ、
どうやら、
サンプルコードでの、各th1 th2 メソッドの中の、
mutex インスタンス生成直後、
InitializeLifetimeService を呼び出すと、40秒でmutexが解放されず、
保持し続けるようです。

System.MarshalByRefObject.InitializeLifetimeService メソッド含め、
オブジェクト生存期間について、再勉強してみます。

お答えくださった皆様、大変ありがとうございました。
1

スキルアップ/キャリアアップ(JOB@IT)