【问题标题】:c# thread infinite loopc#线程无限循环
【发布时间】:2014-11-12 03:23:59
【问题描述】:

我正在尝试使用AutoResetEvent 运行两组线程以相互协调; 在第一组(客户)完成后,我使用thread.join() 确保第一组中的所有线程都已完成,设置标志以停止第二个线程。但是,thread.join() 从未完成,调试器在两者之​​间失去了踪迹。该标志从未设置,因此它继续运行。 有人可以看看这里出了什么问题吗?谢谢!

private static AutoResetEvent tellerFree = new AutoResetEvent(true);
private volatile static bool doneflag = true;
public static void runMultTeller()
{

    List<Thread> custThreads = new List<Thread>();
    List<Thread> tellThreads = new List<Thread>();
    for (int i = 1; i <= 50; i++)
    {
        Thread td = new Thread(getTeller);
        td.Name = Convert.ToString(i);
        custThreads.Add(td);
        td.Start();
    }

    for (int j = 1; j <= 5; j++)
    {
        Thread tt = new Thread(doTelling);
        tt.Name = Convert.ToString(j);
        custThreads.Add(tt);
        tt.Start();
    }

    foreach (Thread tc in custThreads)
    {
        if (tc.IsAlive)
        {
            tc.Join();
        }
    }
    Console.WriteLine("Customer are done");

    doneflag = false;

    foreach (Thread t2 in tellThreads)
    {
        t2.Join();
    }
    Console.WriteLine("Teller are done");

    Console.WriteLine("Done");
    Thread.Sleep(5000);
}

static public void doTelling()
{
    string name = Thread.CurrentThread.Name;
    while (doneflag)
    {
        Console.WriteLine("teller#{0} serving", name);
        Thread.Sleep(500);
        Console.WriteLine("teller#{0} done", name);
        tellerFree.Set();
    }
}

static public void getTeller()
{
    string name = Thread.CurrentThread.Name;
    Console.WriteLine("customer#{0} Enter", name);
    tellerFree.WaitOne();
    Console.WriteLine("customer#{0} Leave", name);

}

【问题讨论】:

  • 你不需要信号量吗?
  • 如果你正在处理这些线程之间的生产者/消费者关系,我建议阅读this article。我不确定这是否是您故意的,但是按照您现在设置的方式,一个生产线程可能会释放所有 50 个消费线程。我想你希望一个线程产生的每个项目都被一个消费线程消费。如果没有,那我道歉。
  • 感谢您的指出。在这个示例中,我没有尝试这样做。也许下次吧。

标签: c# multithreading join


【解决方案1】:

你需要改变:

custThreads.Add(tt);

在第二个“for”循环中:

tellThreads.Add(tt);

否则,您的 Join() 调用将永远等待 doTelling() 线程完成,这将永远不会发生,因为 doneFlag 永远不会被设置。

【讨论】:

  • 我完全错过了。很好看!
  • 愚蠢的我!这就是复制和粘贴时发生的情况。我在调试代码时发现了额外的线程。但是调试器随后消失了(我猜是在等待未完成的线程加入)所以我认为还有其他问题。感谢您的帮助!
【解决方案2】:

将布尔值标记为volatile 并不能确保其他线程立即观察到该更改。它只确保如果其他线程观察到变化,之后它们还将观察由写入 volatile 变量的线程在写入之前完成的所有其他写入。

更多信息请见http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three.aspx

实际上,最后一点是谎言。 volatile 读写的真正语义比我在这里概述的要复杂得多。事实上,它们实际上并不能保证每个处理器都会停止它正在做的事情并更新缓存到主内存/从主内存更新。相反,它们提供了较弱的保证,即可以观察到读取和写入之前和之后的内存访问如何相对于彼此进行排序。某些操作(例如创建新线程、进入锁或使用 Interlocked 系列方法之一)会为观察排序引入更强的保证。如果您想了解更多详细信息,请阅读 C# 4.0 规范的第 3.10 和 10.5.3 节。

坦率地说,我不鼓励你创建一个不稳定的字段。易失性字段表明您正在做一些彻头彻尾的疯狂事情:您试图在两个不同的线程上读取和写入相同的值而没有设置锁定。锁保证在锁内读取或修改的内存被观察到是一致的,锁保证一次只有一个线程访问给定的一块内存,等等。

更新: 正如 Oleg Mikhaylov 所注意到的,问题中的代码比仅使用 volatile 存在更大的问题。纠正该问题后,该程序可能大部分时间都可以运行。尽管如此,我还是把这个答案留在这里,因为volatile 的使用确实是第二个问题。

我还强烈建议您阅读 Joseph Albahari 的《Threading in C#》一书。

【讨论】:

  • @EdS。不,它没有。声明“这确保了字段中始终存在最新的值。”不正确。
  • 哇,如果那是真的,那真是一大疏忽。
  • 感谢 cmets。我首先认为 doneflag 是问题,然后添加了 volatile。这只是为所有线程设置全局变量的一种简单方法,但绝对不是一个好习惯。
  • 我想我不知道,因为我从未在 C# 中使用过它,更喜欢InterlockedExchange。我可以找到与我提供的第一个链接 (msdn.microsoft.com/en-us/library/windows/desktop/…) 存在相同问题的其他地方。我注意到 C# 文档有掩盖重要细节的习惯。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-12
  • 2012-11-14
相关资源
最近更新 更多