如何引发共享资源的同时访问?

4

我正在创建一个组件,用于对输入执行顺序处理。由于它将在几个不同的进程中托管,因此我需要它是线程安全的。起初,我故意从代码中省略了线程安全性。现在是引入这一点的时候了。

首先,我想先进行一些错误测试,但我无法得到预期结果。以下是该处理引擎的简化版本代码:

public Document DoOrchestration(Document input)
{
    Document output = new Document();

    foreach (var orchestrationStep in m_OrchestrationSteps)
    {
        var processor = GetProcessor(orchestrationStep).Clone();

        output = processor.Process(input);

        input = output;
    }

    return output;
}

处理器可以由我们组织中的其他人开发,这可能包括一些复杂的初始化。它们也可能是线程不安全的,因此我使用原型模式获取唯一实例以避免这些线程问题。
为测试该功能,我使用以下代码:
for (int i = 0; i < 20000; i++)
{
    Thread t = new Thread(() => TestOrchestration(i));
    t.Start();
}

void TestOrchestration(int number)
{
    Document doc = new Document(string.Format("Test {0}", number));
    doc = DoOrchestration(doc);
    if (doc.ToString().Substring(0,35) != strExpectedResult)
    {
        System.Console.WriteLine("Error: {0}", doc.ToString();
    }
}

我原本以为一些线程会相互碰撞,导致它们的结果混乱,但令我惊讶的是,这种情况并没有发生。可能有一个简单而合理的解释,但我无法理解。或者是因为这个代码太简单了,不会导致两个线程同时对输入/输出变量进行操作吗?
4个回答

1

请查看CHESS

CHESS是一种用于查找和重现并发程序中Heisenbugs的工具。CHESS反复运行并发测试,确保每次运行都采用不同的交错方式。如果某个交错方式导致错误,CHESS可以重现该交错方式以进行更好的调试。CHESS适用于托管程序和本机程序。


我听说过CHESS,但我试图走捷径。我没有学习如何应用CHESS,而是尝试自己制作一个可能过于简单化的测试应用程序。但从长远来看,投资时间学习CHESS可能是值得的... - Jonas Samuelsson

0

我认为在下一个线程开始之前,您的测试函数几乎已经完成。您可以使所有线程在调用协作函数之前等待ManualResetEventSlim,然后设置ManualResetEventSlim。
这样,所有线程实际上都会尝试同时调用协作。

此外,如果所有线程几乎同时进行 orchestracion 调用,则可能不需要使用 20,000 个线程来模拟此行为。

ManualResetEventSlim manualEvent = new ManualResetEventSlim (false);

for (int i = 0; i < 20000; i++)
{
    Thread t = new Thread(() => TestOrchestration(i));
    t.Start();
}
manualEvent.Set();

void TestOrchestration(int number)
{
    manualEvent.Wait();
    Document doc = new Document(string.Format("Test {0}", number));
    doc = DoOrchestration(doc);
    if (doc.ToString().Substring(0,35) != strExpectedResult)
    {
        System.Console.WriteLine("Error: {0}", doc.ToString();
    }
}

是的,这是一开始没有发现任何并发问题的原因。但是,当信号AutoresetEvent时,只允许一个线程通过。我认为这是设计上的。因此,我以更简单(更天真)的方式做了同样的事情,即让所有线程等待bool值,并在生成所有线程之后设置它。有趣的结果是,在创建新文档以执行编排的行中,参数Number在线程之间发生冲突。但是,实际的编排似乎运作良好。 - Jonas Samuelsson
@jonas-samuelsson 不确定为什么会只让一个线程一次? - coder_bro
@JonasSamuelsson 我应该使用 ManualResetEvent,AutoResetEvent 会被设置回非信号状态,这就是让一个线程的原因。我已经更正了答案使用 ManualResetEventSlim。 - coder_bro

0
我假设由于你的测试函数过于简单,你的线程甚至没有时间在大量生成之前完成其工作。考虑使用屏障来允许所有线程在计算步骤开始之前生成。另外,您需要考虑增加测试用例的复杂性,例如在同一循环中执行多个相同的操作(启动线程是昂贵的,并且允许其他核心在你处理资源争用之前就完成它们的工作)。
通常,通过在更长的时间内快速访问相同的资源来挑衅资源争用,而你的测试用例似乎没有考虑到这点。顺便说一下,我强烈建议你设计时考虑线程安全,而不是后期再引入线程安全。当使用代码时,你比在后期分析代码时更了解资源访问模式。

是的,你说得完全正确。我一开始就应该考虑到线程安全性。然而,在这种情况下,我的意图是创建一个对象池,并在资源管理器中设置守卫条件,而不是在实际编排引擎中设置。通过这种方式,我可以同时运行多个编排引擎,而不是对一个实例进行阻塞调用。 - Jonas Samuelsson

0
你可以使用 ManualResetEvent 来同时继续任意数量的等待线程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接