我想并行化一段代码,但代码实际上变慢了,可能是因为Barrier和BlockCollection的开销。将有2个线程,其中第一个将找到第二个工作的工作。这两项操作都没有多大工作,因此安全切换的开销很快就会超过两个线程。
所以我想我会尝试自己编写一些代码尽可能精简,而不使用Barrier等。但它并不表现一致。有时它有效,有时却没有,我无法弄清楚为什么。
这段代码只是我用来尝试同步两个线程的机制。它没有做任何有用的事情,只需要重现bug所需的最少代码量。
所以这是代码:
// 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();
}
我们的想法是拥有一个工作项链表。一个线程将项添加到该列表的后面,另一个线程读取它们,执行某些操作,并轮询Next字段以查看它是否已设置。一旦设置,它将移动到新的并处理它。它在紧急忙循环中轮询Next字段,因为它应该非常快速地设置。进入睡眠状态,上下文切换等会破坏平行代码的好处。 创建工作项所花费的时间与执行它相当,因此浪费的周期应该非常小。
当我在发布模式下运行代码时,有时它会起作用,有时它什么都不做。问题似乎在'消费者'主题中,'创建'主题似乎总是完成。 (您可以通过摆弄Console.WriteLines来检查)。 它一直在调试模式下工作。在发布它大约50%命中和错过。添加一些Console.Writelines有助于成功率,但即便如此,它也不是100%。 (#define VERBOSE的东西)。
当我在'Consumer'线程中添加Thread.Sleep(1)时,它似乎也解决了它。但是无法重现错误与确定它已经修复不一样。
这里有没有人知道这里出了什么问题?是创建本地副本还是某些无法更新的优化?那样的东西?
没有部分更新权利吗?就像一个datarace,但是那一个线程是写一半,而另一个线程读取部分写入的内存?只是检查..
看着它我认为它应该可以工作..我想每隔几次线程以不同的顺序到达并使其失败,但我不知道如何。我怎么能解决这个问题而不加减速呢?
提前感谢任何提示,
格特 - 扬
答案 0 :(得分:2)
我尽我所能避免不惜一切代价完全关闭/堆叠互动的雷区。 这可能是一个(语言级)竞争条件,但没有反映Parallel.Invoke我不能确定。基本上,有时fst会被create()更改,有时则不会。理想情况下,它永远不应该被更改(如果c#具有良好的关闭行为)。这可能是由于Parallel.Invoke选择在哪个线程上运行create()和consume()。如果create()在主线程上运行,它可能会在consume()获取它的副本之前更改fst。或者create()可能在一个单独的线程上运行并获取fst的副本。基本上,就像我喜欢c#一样,在这方面它是一个彻头彻尾的痛苦,所以只需解决它并将闭包中涉及的所有变量视为不可变的。
让它运作起来:
//Replace
WorkItem fst = null
//with
WorkItem fst = WorkItem.GetSpecialBlankFirstItem();
//And
if (fst == null) fst = tmp;
//with
if (fst.Next == null) fst.Next = tmp;
答案 1 :(得分:1)