2

実行に時間がかかる可能性があるスレッド間でグローバル静的変数を共有する必要があるマルチスレッド プログラム (C#) があります (WCF を使用して別のシステムにデータ要求を送信します)。問題は、lock ステートメントを使用しても、ThreadPool の外部で宣言されている場合、相互排除が保証されないように見えることです。

static void Main(string[] args)
{
    public static int globalVar = 0;
    public object locker;

    System.Timers.Timer timer1 = new System.Timers.Timer(1000);
    timer1.Elapsed += new ElapsedEventHandler(onTimer1ElapsedEvent);
    timer1.Interval = 1000;
    timer1.Enabled = true;

    System.Timers.Timer timer2 = new System.Timers.Timer(500);
    timer2.Elapsed += new ElapsedEventHandler(onTimer2ElapsedEvent);
    timer2.Interval = 500;
    timer2.Enabled = true;
}

public void onTimer1ElapsedEvent(object source, ElapsedEventArgs e)
{
    lock (locker) {
        ThreadPool.QueueUserWorkItem(new WaitCallback(state => 
        {
            globalVar = 1;
            Console.WriteLine("Timer1 var = {0}", globalVar);
        }));
    } 
}
public void onTimer2ElapsedEvent(object source, ElapsedEventArgs e)
{
    lock (locker) {
        ThreadPool.QueueUserWorkItem(new WaitCallback(state => 
        {
            globalVar = 2;
            Thread.Sleep(2000);  // simulates a WCF request that may take time
            Console.WriteLine("Timer2 var = {0}", globalVar);
        }));
    } 
}

したがって、ロックは機能せず、プログラムは出力できます: Timer2 var = 1

The lock ステートメントを ThreadPool 内に配置すると、問題が解決するようです。

public void onTimer1ElapsedEvent(object source, ElapsedEventArgs e)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(state =>
    {
        lock (locker) {           
            globalVar = 1;
            Console.WriteLine("Timer1 var = {0}", globalVar);
        } 
    }));    
}
public void onTimer2ElapsedEvent(object source, ElapsedEventArgs e)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(state =>
    {
        lock (locker) {           
            globalVar = 2;
            Thread.Sleep(2000);  // simulates a WCF request that may take time
            Console.WriteLine("Timer2 var = {0}", globalVar);
        } 
    }));    
}

ただし、2つのアプローチの違いと、同じ動作が得られない理由はわかりません。

また、2 番目のアプローチは相互排除の問題を解決しますが、timer1 スレッドは timer2 がロック ステートメントを終了するまで常に待機する必要があるため (これには時間がかかります)、私のプログラムではマルチスレッドの概念が機能しなくなります。共有変数を使用することと並行してマルチスレッド化を行うための最良の解決策は何ですか?

4

3 に答える 3

2

そのような変数を更新するためにロックは必要ありません。たとえば、次のように置き換えることができます。

lock (locker)
{
    globalVar = 1;
    Console.WriteLine("Timer1 var = {0}", globalVar);
}

と:

int val = 1;
globalVar = val;
Console.WriteLine("Timer1 var = {0}", val);

プリミティブ型への書き込みはアトミックであることが保証されているため、ここでロックする必要はありません。

ここで、値を増やしたい場合は、次のように記述できます。

int val = Interlocked.Increment(ref globalVar);

以下を追加することもできます。

int val = Interlocked.Add(ref globalVar, 100);

繰り返しますが、これらはロックを必要としません。

インターロッククラスをご覧ください。

于 2013-08-23T21:17:55.143 に答える
1

より短くより賢明な解決策は、(さらに別の) 余分なスレッドを使用してタイマーを実行しないことです。System.Timers.Timer既にプール スレッドを割り当てています。

public void onTimer1ElapsedEvent(object source, ElapsedEventArgs e)
{
    lock (locker) {
            globalVar = 1;
            Console.WriteLine("Timer1 var = {0}", globalVar);

    } 
}
public void onTimer2ElapsedEvent(object source, ElapsedEventArgs e)
{
    lock (locker) {
            globalVar = 2;
            Thread.Sleep(2000);  // simulates a WCF request that may take time
            Console.WriteLine("Timer2 var = {0}", globalVar);
    } 
}

あなたの混乱は、「ロックステートメントを ThreadPool 内に配置する」などの定式化から生じています。

メソッド内に lock ステートメントを配置して、メソッドが実行されるスレッドを制御します。

于 2013-08-20T21:20:39.043 に答える
1

最初のシナリオでは、ロックしているのは、新しい WaitCallback を ThreadPool に追加することだけです。ThreadPool を行と考えてください。あなたが行ったのは、他の誰かを行に入れることをロックすることだけです (皮肉なことに、ThreadPool 自体が維持する内部キューをロックするため、これは実際には二重の作業です)。ThreadPool が後で実行するコードは、別のスレッド上にあり、別の時間に発生し、そのロックとは何の関係もありません。

2 番目のシナリオでは、ロックは実際は ThreadPool スレッドが実行しているコード内にあるため、予期されるロック セマンティクスが表示されます。

ただし、一般的には、回避できる場合は ThreadPool スレッドでロックしないことをお勧めします。ThreadPool は (理想的には) 高速実行タスクに使用する必要があります。共有状態の性質と用途、および達成しようとしている内容によって異なりますが、一般的には、可能であればタスクPLINQを使用することを選択します。

于 2013-08-20T21:14:11.970 に答える