2

我有一个多线程程序(C#),我必须在线程之间共享全局静态变量,这可能需要一些时间来执行(使用 WCF 将数据请求发送到另一个系统)。问题是,当在线程池之外声明时,使用 lock 语句似乎不能保证互斥。

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

将 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);
        } 
    }));    
}

但是,我不了解这两种方法之间的区别以及为什么它不会产生相同的行为。

此外,第二种方法解决了互斥问题,但 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

在您的第一个场景中,您所锁定的只是在 ThreadPool 上添加一个新的 WaitCallback。将 ThreadPool 视为一条线:您所做的只是锁定让其他人排队(具有讽刺意味的是,这实际上是双重工作,因为 ThreadPool 本身锁定在它维护的内部队列上)。ThreadPool 之后执行的代码在不同的线程上,发生在不同的时间,并且不再与那个锁有任何关系。

在第二种情况下,锁实际上ThreadPool 线程正在执行的代码中,这就是为什么您会看到预期的锁定语义。

但是,一般来说,如果可以避免的话,我建议不要锁定 ThreadPool 线程。应该(理想情况下)将 ThreadPool 用于快速运行的任务。这取决于共享状态的性质和使用,以及您要完成的工作,但总的来说,我会尽可能选择使用Tasks和/或PLINQ

于 2013-08-20T21:14:11.970 回答
1

更短更明智的解决方案是不使用(又一个)额外线程来执行 Timer。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);
    } 
}

您的困惑来自诸如“将锁定语句放入线程池”之类的公式。

您将锁定语句放在方法中以控制它们运行的​​线程。

于 2013-08-20T21:20:39.043 回答