简体   繁体   English

为什么有时并行执行此代码?

[英]Why does parallel this code work sometimes?

I wanted to parallelize a piece of code, but the code actually got slower probably because of overhead of Barrier and BlockCollection. 我想并行化一段代码,但实际上可能由于Barrier和BlockCollection的开销而使代码变慢。 There would be 2 threads, where the first would find pieces of work wich the second one would operate on. 将有2个线程,第一个线程将找到工作,第二个线程将在其上进行工作。 Both operations are not much work so the overhead of switching safely would quickly outweigh the two threads. 两项操作都工作量不大,因此安全切换的开销很快就会超过两个线程。

So I thought I would try to write some code myself to be as lean as possible, without using Barrier etc. It does not behave consistent however. 因此,我想我会尝试自己编写一些代码,使其尽可能地精简,而不使用Barrier等。但是,它的行为并不一致。 Sometimes it works, sometimes it does not and I can't figure out why. 有时它起作用,有时却不起作用,我不知道为什么。

This code is just the mechanism I use to try to synchronize the two threads. 这段代码只是我尝试使两个线程同步的机制。 It doesn't do anything useful, just the minimum amount of code you need to reproduce the bug. 它没有做任何有用的事情,只是重现该错误所需的最少代码量。

So here's the code: 所以这是代码:

    // node in linkedlist of work elements
        class WorkItem {
            public int Value;
            public WorkItem Next;
        }

        static void Test() {

            WorkItem fst = null; // first element

            Action create = () => {
                WorkItem cur=null;
                for (int i = 0; i < 1000; i++) {                    

                    WorkItem tmp = new WorkItem { Value = i }; // create new comm class

                    if (fst == null) fst = tmp; // if it's the first add it there
                    else cur.Next = tmp;        // else add to back of list

                    cur = tmp; // this is the current one
                }
                cur.Next = new WorkItem { Value = -1 }; // -1 means stop element
#if VERBOSE
                Console.WriteLine("Create is done");
#endif
            };

            Action consume = () => {
                //Thread.Sleep(1); // this also seems to cure it
#if VERBOSE
                Console.WriteLine("Consume starts"); // especially this one seems to matter
#endif

                WorkItem cur = null;
                int tot = 0;

                while (fst == null) { } // busy wait for first one
                cur = fst;
#if VERBOSE
                Console.WriteLine("Consume found first");
#endif
                while (true) {
                    if (cur.Value == -1) break; // if stop element break;
                    tot += cur.Value;

                    while (cur.Next == null) { } // busy wait for next to be set
                    cur = cur.Next; // move to next
                } 
                Console.WriteLine(tot);
            };

            try { Parallel.Invoke(create, consume); }
            catch (AggregateException e) {
                Console.WriteLine(e.Message);
                foreach (var ie in e.InnerExceptions) Console.WriteLine(ie.Message);
            }

            Console.WriteLine("Consume done..");
            Console.ReadKey();
        }

The idea is to have a Linkedlist of workitems. 这个想法是要有一个工作项的链表。 One thread adds items to the back of that list, and another thread reads them, does something, and polls the Next field to see if it is set. 一个线程将项目添加到该列表的末尾,另一个线程读取它们,执行某些操作,然后轮询Next字段以查看是否已设置。 As soon as it is set it will move to the new one and process it. 一旦设置,它将移至新的并进行处理。 It polls the Next field in a tight busy loop because it should be set very quickly. 它会在紧密的繁忙循环中轮询“下一个”字段,因为它应该设置得很快。 Going to sleep, context switching etc would kill the benefit of parallizing the code. 进入睡眠,上下文切换等将杀死使代码并行化的好处。 The time it takes to create a workitem would be quite comparable to executing it, so the cycles wasted should be quite small. 创建工作项所需的时间与执行工作项的时间相当,因此浪费的周期应该很小。

When I run the code in release mode, sometimes it works, sometimes it does nothing. 当我在发布模式下运行代码时,有时它可以工作,有时却什么也没做。 The problem seems to be in the 'Consumer' thread, the 'Create' thread always seems to finish. 问题似乎出在“消费者”线程中,“创建”线程似乎总是完成了。 (You can check by fiddling with the Console.WriteLines). (您可以通过熟悉Console.WriteLines进行检查)。 It has always worked in debug mode. 它一直在调试模式下工作。 In release it about 50% hit and miss. 在发行中,命中率和失误率约为50%。 Adding a few Console.Writelines helps the succes ratio, but even then it's not 100%. 添加一些Console.Writelines有助于提高成功率,但即使如此,它也不是100%。 (the #define VERBOSE stuff). (#define VERBOSE的东西)。

When I add the Thread.Sleep(1) in the 'Consumer' thread it also seems to fix it. 当我在“消费者”线程中添加Thread.Sleep(1)时,它似乎也可以对其进行修复。 But not being able to reproduce a bug is not the same thing as knowing for sure it's fixed. 但是无法重现错误与确定已修复错误并不相同。

Does anyone here have a clue as to what goes wrong here? 这里有人知道这里出了什么问题吗? Is it some optimization that creates a local copy or something that does not get updated? 是创建本地副本的某些优化还是未更新的优化? Something like that? 那样的东西?

There's no such thing as a partial update right? 没有部分更新的权利吗? like a datarace, but then that one thread is half doen writing and the other thread reads the partially written memory? 就像datarace,但是那一个线程写了一半,而另一个线程读了部分写的内存? Just checking.. 只是检查..

Looking at it I think it should just work.. I guess once every few times the threads arrive in different order and that makes it fail, but I don't get how. 看着它,我认为它应该工作。.我猜线程每隔几次以不同的顺序到达,这会使它失败,但是我不知道如何。 And how I could fix this without adding slowing it down? 以及如何在不增加速度的情况下解决此问题?

Thanks in advance for any tips, 预先感谢您提供的任何提示,

Gert-Jan 格特 - 扬

I do my damn best to avoid the utter minefield of closure/stack interaction at all costs. 我尽最大努力避免不惜一切代价避免完全的雷区/烟囱相互作用。 This is PROBABLY a (language-level) race condition, but without reflecting Parallel.Invoke i can't be sure. 这可能是一个(语言级别的)竞争条件,但没有反映Parallel.Invoke,我不确定。 Basically, sometimes fst is being changed by create() and sometimes not. 基本上,有时fst会被create()更改,有时不会。 Ideally, it should NEVER be changed (if c# had good closure behaviour). 理想情况下,永远不要更改它(如果C#具有良好的闭包行为)。 It could be due to which thread Parallel.Invoke chooses to run create() and consume() on. 这可能是由于Parallel.Invoke选择在哪个线程上运行create()和consume()。 If create() runs on the main thread, it might change fst before consume() takes a copy of it. 如果create()在主线程上运行,则它可能会在stant()获取它的副本之前更改fst。 Or create() might be running on a separate thread and taking a copy of fst. 或者create()可能在单独的线程上运行并获取fst的副本。 Basically, as much as i love c#, it is an utter pain in this regard, so just work around it and treat all variables involved in a closure as immutable. 基本上,就我所喜欢的C#而言,这在这方面是非常痛苦的,因此只需解决它,并将闭包中涉及的所有变量视为不可变的。

To get it working: 要使其正常工作:

//Replace 
WorkItem fst = null
    //with
WorkItem fst = WorkItem.GetSpecialBlankFirstItem();

//And 
if (fst == null) fst = tmp;
   //with
if (fst.Next == null) fst.Next = tmp;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM