C#线程竞争条件

时间:2010-12-08 12:53:41

标签: c# multithreading

我正在尝试编写一个函数,为每个“联系人”启动一个线程,然后(通过网络)查询该联系人的结果。我希望我的等待函数最多等待1.5秒以获得响应,之后,只需终止所有剩余的线程。

我遇到的问题是函数在所有线程完成之前返回,即使根据逻辑,这也不可能。 main函数中的while循环应该等待所有线程完全完成,但我得到以下输出:

FAIL: Storage test 1 exists 0 times in the DHT.
    : Storage test 2 exists 0 times in the DHT.
Added storage test 1 to the entries.
Added storage test 2 to the entries.

(FAIL行来自主要测试程序,看看Get()返回了多少结果。)

据我所知,这是不可能的。有没有人知道竞争条件可能发生在哪里(或者我做过的任何其他假设都不正确)?

功能定义如下:

    public IList<Entry> Get(ID key)
    {
        ConcurrentBag<Entry> entries = new ConcurrentBag<Entry>();
        List<Thread> threads = new List<Thread>();
        foreach (Contact c in this.p_Contacts)
        {
            Thread t = new Thread(delegate()
            {
                try
                {
                    FetchMessage fm = new FetchMessage(this, c, key);
                    fm.Send();
                    int ticks = 0;

                    // Wait until we receive data, or timeout.
                    while (!fm.Received && ticks < 1500)
                    {
                        Thread.Sleep(100);
                        ticks += 100;
                    }

                    if (fm.Received)
                    {
                        foreach (Entry e in fm.Values)
                        {
                            Console.WriteLine("Added " + e.Value + " to the entries.");
                            entries.Add(e);
                        }

                        if (entries.Count == 0)
                            Console.WriteLine("There were no entries to add.");
                    }
                    else
                        Console.WriteLine("The node did not return in time.");
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
            );
            t.IsBackground = false;
            t.Start();
        }

        while (true)
        {
            bool stopped = true;
            foreach (Thread t in threads)
            {
                if (t.ThreadState != ThreadState.Stopped)
                    stopped = false;
            }
            if (stopped)
                break;
            Thread.Sleep(100);
        }

        return new List<Entry>(entries.ToArray());
    }

4 个答案:

答案 0 :(得分:5)

看起来你永远不会将Thread(t)放在List<Thread>(线程)中。您的foreach循环永远不会执行。

主线程只等待100毫秒并继续。

答案 1 :(得分:2)

@Toby有正确的答案,但如果我可以介绍一些其他的东西来改进代码。从本质上讲,您是手动管理自己的ThreadPool和超时 - 这是.Net为您提供的开箱即用的功能。请参阅:http://msdn.microsoft.com/en-us/library/system.threading.threadpool(v=VS.100).aspx

如果将ThreadPool与.Net 4 Barrier结合使用,可以大大简化代码。从本质上讲,障碍会阻止所有线程,直到它们同步。当您向线程传递相同的障碍并在最后同步时,您可以暂停主线程直到完成所有工作线程。重构的代码如下所示:

// For the number of threads + 1 for the main thread
Barrier barrier = new Barrier(this.p_Contacts.count + 1);
ConcurrentBag<Entry> entries = new ConcurrentBag<Entry>();

foreach (Contact c in this.p_Contacts)
{
    ThreadPool.RegisterWaitForSingleObject(
        new EventWaitHandle(false, EventResetMode.AutoReset),
        (stateInfo,timedOut) => {
            try
            {
                FetchMessage fm = new FetchMessage(this, c, key);
                fm.Send();

                while(!fm.Received || !timedOut)
                {
                    Thread.Sleep(100);
                }

                if(fm.Received)
                {
                    foreach (Entry e in fm.Values)
                    {
                        entries.Add(e);
                        Console.WriteLine("Added " + e.Value + " to the entries.");
                    }

                    // avoid counting other thread's work
                    if (fm.Values.count == 0)
                    {
                        Console.WriteLine("There were no entries to add.");
                    }
                }
                else
                {
                    Console.WriteLine("The node did not return in time.");
                }

                barrier.SignalAndWait();
            }
            catch(Exception e)
            {
                Console.WriteLine(e);
            }
        }, null, TimeSpan.FromSeconds(1.5), true);
    );
}

// This limits total time waited to only 1.5 seconds
barrier.SignalAndWait(TimeSpan.FromSeconds(1.5));

return new List<Entry>(entries.ToArray());

不要像你一样手动管理旋转锁,而是让.Net为你做。

答案 2 :(得分:0)

线程没有添加到列表中,因此while循环会立即中断?

答案 3 :(得分:0)

此问题的解决方案是使用ConcurrentDictionary来跟踪线程已完成的联系人:

    public IList<Entry> Get(ID key)
    {
        ConcurrentBag<Entry> entries = new ConcurrentBag<Entry>();
        ConcurrentDictionary<Contact, bool> done = new ConcurrentDictionary<Contact, bool>();
        List<Thread> threads = new List<Thread>();
        foreach (Contact c in this.p_Contacts)
        {
            Thread t;
            ThreadStart ts = delegate()
            {
                try
                {
                    FetchMessage fm = new FetchMessage(this, c, key);
                    fm.Send();
                    int ticks = 0;

                    // Wait until we receive data, or timeout.
                    while (!fm.Received && ticks < 1500)
                    {
                        Thread.Sleep(100);
                        ticks += 100;
                    }

                    if (fm.Received)
                    {
                        foreach (Entry e in fm.Values)
                        {
                            Console.WriteLine("Added " + e.Value + " to the entries.");
                            entries.Add(e);
                        }

                        if (entries.Count == 0)
                            Console.WriteLine("There were no entries to add.");
                    }
                    else
                        Console.WriteLine("The node did not return in time.");

                    Thread.MemoryBarrier();
                    done[c] = true;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            };
            t = new Thread(ts);
            done[c] = false;
            t.IsBackground = true;
            t.Start();
        }

        while (true)
        {
            bool stopped = true;
            foreach (Contact c in this.p_Contacts)
            {
                if (!done[c])
                    stopped = false;
            }
            if (stopped)
                break;
            Thread.Sleep(100);
        }

        return new List<Entry>(entries.ToArray());
    }