【发布时间】:2011-09-03 00:42:24
【问题描述】:
我想并行化一段代码,但代码实际上变慢了,可能是因为 Barrier 和 BlockCollection 的开销。将有 2 个线程,第一个线程会在其中找到第二个线程将运行的工作。这两个操作的工作量都不大,因此安全切换的开销很快就会超过两个线程。
所以我想我会尝试自己编写一些代码以尽可能精简,而不使用 Barrier 等。但是它的行为并不一致。有时有效,有时无效,我不知道为什么。
这段代码只是我用来尝试同步两个线程的机制。它没有做任何有用的事情,只是重现错误所需的最少代码量。
代码如下:
// 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 的东西)。
当我在“消费者”线程中添加 Thread.Sleep(1) 时,它似乎也可以修复它。但无法重现错误与确定已修复是两回事。
这里有没有人知道这里出了什么问题?是创建本地副本的一些优化还是没有更新的东西?类似的东西?
不存在部分更新之类的东西,对吧?就像一个数据竞赛,但是一个线程写了一半,另一个线程读取了部分写的内存?只是检查..
看着它,我认为它应该可以工作。我猜每隔几次线程以不同的顺序到达,这会导致它失败,但我不明白如何。以及如何在不减慢速度的情况下解决此问题?
提前感谢您的任何提示,
格特-简
【问题讨论】:
-
如果您的并行代码有时可以工作,那么您就有了所谓的竞争条件。
-
我知道竞态条件,这就是关于“部分更新”的评论。很确定这不是一个。
-
你说
//Thread.Sleep(1); // this also seems to cure it的事实本身就证明它是一个竞争条件。 -
你说得对,我对数据竞赛感到困惑。
标签: c# performance synchronization parallel-processing task-parallel-library