运行多个并发线程的后果?

时间:2014-05-08 21:17:09

标签: c# multithreading .net-4.0

在过去的几个小时里,我一直在用这个打击我的头,所以这里就是这样。对于那些对多线程经验不足的人来说,这可能是一个常见错误吗?谁知道呢。

在包含的代码中,我实例化了3个运行方法DisplayValues(DateTime Now, int Period)的线程。调试器在每个if语句中停止三次,对于每个语句,它使用正确的值进行方法调用。问题是Console.WriteLine显示的是不稳定的值,与调用的方式完全不同。

控制台使用以下参数调用DisplayValues() 3次,这是正确的:     DisplayValues('{5/8/2014 4:20:00 AM}', 0);     DisplayValues('{5/8/2014 4:35:00 AM}', 1);     DisplayValues('{5/8/2014 4:50:00 AM}', 2);

但输出完全不同:

5/8/2014 4:35:00 AM期间:0

5/8/2014 4:50:00 AM期间:1

5/8/2014 4:51:00 AM期间:2

调试器确认了这一点。由于它是一个控制台应用程序,我认为可能所有方法都是静态的,所以我将DisplayValues()移到了一个类。然后我认为所有三个类实例都有相同的名称,所以我更改了名称。然后我认为它可能是CancellationTokenSource对象,所以我也删除了它。

毋庸置疑,如果没有线程,输出就是正确的。

我知道有一个明显的原因,我只是不知道它是什么。

感谢任何帮助。 感谢。

bool thread0Running = false;
bool thread1Running = false;
bool thread2Running = false;
DateTime DateNow = new DateTime(2014, 5, 8, 4, 0, 0);

while ((!thread0Running || !thread1Running || !thread2Running) && DateNow.Hour == 4)
{
    if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 20))
    {
        thread0Running = true;
        Class myClass0 = new Class();
        new Thread(() => myClass0.DisplayValues(DateNow, 0, cts0.Token)).Start();

    }
    else if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 35))
    {
        thread1Running = true;
        Class myClass1 = new Class();
        new Thread(() => myClass1.DisplayValues(DateNow, 1, cts1.Token)).Start(); 
    }
    else if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 50))
    {
        thread2Running = true;
        Class myClass2 = new Class();
        new Thread(() => myClass2.DisplayValues(DateNow, 2, cts2.Token)).Start();
    }
    DateNow = DateNow.AddMinutes(1);
}
public void DisplayValues(DateTime Now, int Period, Object obj)
{
        Console.WriteLine(Now.ToString() + " Period: " + Period.ToString());
}

4 个答案:

答案 0 :(得分:1)

Thread.Start并不意味着线程立即开始运行,它导致操作系统将当前实例的状态更改为ThreadState.Running。一旦线程处于ThreadState.Running状态,操作系统就可以将其安排执行,但它并不意味着首先创建的线程将首先被执行。这就是问题的原因。

如果要使3线程按顺序运行,则应查看线程同步。

答案 1 :(得分:1)

正如其他人已经指出的那样,Console.WriteLine可能比变量的增加慢。解决此问题的另一种方法是使用线程局部变量。它们不应受其他线程更改的影响。对于C#,我找到了这个链接:http://msdn.microsoft.com/en-us/library/dd642243(v=vs.110).aspx

这种方法的优点是,您不必维护与线程一样多的变量,并且可以同时完成线程的工作。

祝你好运!

答案 2 :(得分:0)

我认为原因是主线程在更改DateNow的同时,其他线程读取相同的值。

考虑一下: 其中一个条件为true - 因此创建了一个新线程,但是当该线程将被安排运行时,您无法控制。 所以同时 - 你的主线程改变了DateNow ...所以..当新创建的线程实际运行时 - 他看到并打印的值 - 与通过条件的“sane”值不同...

考虑一下这个更奇怪的结论: 当写入32位或更少的变量时,C#提供atomicy

Atomicy意味着(以非常一般和不准确的方式)操作不能在中间中断... 所有线程获得一些CPU,然后操作系统停止它,并安排另一个线程运行..在那之后的一段时间 - 操作系统将再次安排我们的线程,它将从它停止的地方继续。 原子操作不能在中间停止......它要么尚未开始,要么已经完成。

但DateTime实际上是64位..这意味着操作系统可以在写入新值时中断主线程。这意味着直到主线程再次被调度 - DateNow将有一些奇怪的,不一致的值 - 并且任何其他线程都可以同时读取该值。

答案 3 :(得分:0)

由于您正在为线程函数使用lambda表达式,因此直到线程执行开始后的某个时间才会复制DateNow的值。由于线程之间没有同步,这是完全不可预测的。在您开始创建第二个线程然后显示DateNow的当前值(4:35)之前,第一个线程(Period 0)可能不会获得任何cpu。第1期会发生同样的情况,然后在添加1分钟后再次绕过循环时,第2期最终会运行。切换每个lambda以使用它自己的变量:

if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 20))
{
    DateTime DateNow0 = DateNow; // DAteTime is a struct so this is a value copy
    thread0Running = true;
    Class myClass0 = new Class();
    new Thread(() => myClass0.DisplayValues(DateNow0, 0, cts0.Token)).Start();

}

将DateNow1和DateNow2用于其他2个块。有了这个改变,我想你会得到你期望的输出。 DateNow的当前值的副本现在发生在主执行线程中的可预测位置。但是,由于无法保证三个线程将按创建顺序运行,因此订单可能仍然无法正确显示。