【问题标题】:Busy Wait with Threads忙于等待线程
【发布时间】:2012-03-08 18:15:57
【问题描述】:

基本上,我需要忙着等到一些 html 出现在网页上。我创建了以下代码来忙着等我:

public void ExecuteBusyWaitThreads()
    {

        foreach (Canidate canidate in allCanidates)
        {
            Thread newThread = new Thread(delegate()
            {
                BusyWait(canidate);
            });

            newThread.Start();
        }
    }

    public bool BusyWait(Canidate canidate)
    {
        //hit that url, and wait for the claim all button to appear
        string page = null;
        while (found == false)
        {
            HttpWebRequest request = Canidate.GetHTTPRequest(canidate.URL);
            //make sure we add the authentication cookes to the request
            request = Canidate.AddCookiesToRequest(request, canidate.GetCookies());
            page = new Canidate().GetPage(request);
            if (page.ToLower().Contains("claim all"))
            {
                found = true;
                NotifyAllThreads();
            }
        }
        return true;
    }

所以,如果我有 8 个canidates,它将产生 8 个线程,每个线程都在寻找出现在网页上的 claim allfound 是一个全局变量。一旦其中一个线程找到claim all,它们都应该保释。

我对这种方法有几个问题。首先,它是一个很好的方法。其次,每个线程会得到它自己的忙等待函数的“副本”。我的意思是,一个线程可以抢占另一个线程并更改该函数中的数据,还是每个线程都获得函数内部声明的变量的副本。请注意,这两个函数都在同一个对象内。

【问题讨论】:

  • 有效的技术问题,但看起来像是试图在扑克或拍卖中作弊。

标签: c# .net multithreading thread-safety


【解决方案1】:

在我回答你的问题之前,我必须指出你犯下了closing over the loop variable的恶劣行为。

首先,这是一个很好的方法。

不,不是真的。随意创建线程通常不是一个好主意。最好使用线程池技术。这可以通过ThreadPool.QueueUserWorkItemTask 类来完成。

其次,每个线程会得到它自己的忙等待函数的“副本”。 我的意思是,一个线程可以抢占另一个线程并更改其中的数据吗? 该函数,或者他们每个人都获得声明的变量的副本 在函数内部。

BusyWait 的每个运行实例都将获得所有局部变量的自己的副本(即pagerequest)。由于found 是在非本地范围内声明的(大概无论如何),因此它将在BusyWait 的所有运行实例之间共享。因此,您当前对found 的读取和写入不是线程安全的,因为没有同步机制。

【讨论】:

  • 感谢您的回答。您可能已经解决了我看到的另一个错误。只是为了让我正确理解这篇文章。在我的 foreach 循环中,我需要创建一个保存候选者的新变量,然后将其传递给 BusyWait 函数?
  • 很好,我完全错过了。 (荷马很好,但可能被认为是顶级的)。
  • @user489041:没错。闭包捕获变量而不是。当前形式的foreach 构造为整个循环创建了一个变量。这就是为什么您需要自己创建一个单独的 变量 才能使其正常工作。 C# 5.0 将更改 foreach 构造的行为,以在每次迭代时自动创建一个单独的变量
【解决方案2】:

其次,每个线程是否会得到自己的忙等待函数“副本”

每个线程都将使用自己的堆栈空间执行函数,这意味着函数内部的任何本地变量都将属于它正在运行的线程。如果你有一个全局变量,比如你的 found 变量在函数内部被改变,你需要设置一个同步机制,这样多个线程就不会同时访问它,因为这会导致很难找到错误还有很多你永远不想想象的恐怖经历!

【讨论】:

    【解决方案3】:

    所有线程都有自己的局部变量副本(在这种情况下只有string page)。

    您的共享 found 变量应声明为 volatile

    这是一种罕见的情况,调用Thread.Sleep() 可能会有所帮助。在对同一站点的调用之间插入一点喘息。

    【讨论】:

    • 我确实想过这样做。但是,您能否详细说明添加 Thread.Sleep() 的好处。我的申请实际上是在与其他人竞争。它首先检测到“声明所有”按钮非常重要。添加 Thread.Sleep() 会帮助还是伤害这种情况?谢谢
    • 短暂的 Sleep() 将减轻您系统的其余部分,并使处理 8 个任务更加平等。但是要在最后一毫秒内保持竞争力,请不要使用 Sleep()。
    【解决方案4】:

    每个线程都使用自己的变量副本运行。

    但是我会修改我的 appoarch。使用找到的变量不是线程安全的。一次可能会发现不止一个线程正在更改。也很有可能一个线程正在读取它,而另一个线程正在写入它。 [lock][1] 可以避免这种情况。

    解决此问题的更好方法是使用EventWaitHandle。这样你就不必担心锁定,你可以建立一个睡眠或超时,所以如果没有出现'claim-all',你的线程不会运行比你希望的更长的时间。

    internal class ExampleOnExecute
    {
        private static EventWaitHandle _stopEvent;
    
        public static EventWaitHandle StopEvent
        {
            get { return _stopEvent ?? (_stopEvent = new EventWaitHandle(false, EventResetMode.ManualReset)); }
        }
    
        public static void SpinOffThreads(IEnumerable<object> someCollection)
        {
            foreach(var item in someCollection)
            {
                // You probably do not want to manualy create a thread since these ideally would be small workers
                // and action BeingInvoke runs in the ThreadPool
                Action<object> process = BusyWait;
    
                process.BeginInvoke(item, null, null);
            }
        }
    
        private static void BusyWait(object obj)
        {
            // You can wait for however long you like or 0 is not waiting at all
            const int sleepAmount = 1;
    
            //     Blocks the current thread until the current instance receives a signal, using
            //     a System.TimeSpan to specify the time interval.
            //
            // Parameters:
            //   timeout:
            //     A System.TimeSpan that represents the number of milliseconds to wait, or
            //     a System.TimeSpan that represents -1 milliseconds to wait indefinitely.
            //
            // Returns:
            //     true if the current instance receives a signal; otherwise, false.
            while (!StopEvent.WaitOne(TimeSpan.FromMilliseconds(sleepAmount)))
            {
                // Do you work here
                var foundIt = DidIFindIt();
    
                if (foundIt)
                {
                    // Signal all threads now to stop working we found it.
                    StopEvent.Set();
                }
            }
        }
    
        private static bool DidIFindIt()
        {
            return true;
        }
    }
    

    Here 是一本关于threading 的优秀免费书。

    【讨论】:

      猜你喜欢
      • 2015-12-08
      • 2015-07-04
      • 1970-01-01
      • 1970-01-01
      • 2021-12-26
      • 2013-10-15
      • 1970-01-01
      • 2022-11-17
      • 1970-01-01
      相关资源
      最近更新 更多