我有一个多线程程序(C#),我必须在线程之间共享全局静态变量,这可能需要一些时间来执行(使用WCF向另一个系统发送数据请求)。问题是当在ThreadPool之外声明时,使用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完成他的锁定语句(这需要时间),因此多线程概念在我的程序中不再起作用。我想知道多线程在使用共享变量的同时完成工作的最佳解决方案是什么?
答案 0 :(得分: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);
同样,这些不需要锁。
查看Interlocked课程。
答案 1 :(得分:1)
在你的第一个场景中,所有你要锁定的是在ThreadPool上增加一个新的WaitCallback。把ThreadPool想象成一条线:你所做的就是锁定将其他人排成一行(具有讽刺意味的是,因为ThreadPool本身锁定了它维护的内部队列,所以实际上是双重工作)。之后ThreadPool执行的代码位于不同的线程上,在不同的时间发生,并且与该锁无关。
在第二个场景中,锁实际上是 in ThreadPool线程正在执行的代码,这就是为什么你会看到预期的锁定语义。
但是,一般情况下,如果可以避免,我建议不要锁定ThreadPool线程。 ThreadPool应该(理想情况下)用于快速运行的任务。这取决于共享状态的性质和用途,以及您要完成的任务,但一般情况下,我会选择尽可能使用Tasks和/或PLINQ。
答案 2 :(得分: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);
}
}
您的困惑来自于“将锁定语句置于ThreadPool中”等公式。
将lock语句放在方法中以控制它们运行的线程。