C#Ordered Threads plus Concurrency

时间:2013-11-17 03:39:57

标签: c# multithreading

我正在使用线程构建管道循环。周期是Fetch,Decode,Execute和Write Back。我编写了一个GUI按钮,触发每个循环。我遇到的问题是当我希望它以特定顺序(F-> D-> X-> W)时输出全部混乱。另外,我希望这些线程同时运行,这意味着我不能使用带监视器的锁。根据我对监视器和锁的了解,他们一次运行一个线程,这对我不起作用。以下是我的代码的简单概述:

public void nextInstruction() 
{
    // These all trigger at once
    // and run in parallel
    fetchEvent.Set();
    decodeEvent.Set();
    executeEvent.Set();
    wbEvent.Set();
}

public void fetch() 
{
    fetchEvent.waitone();
    // do stuff...
}

public void decode()
{
    decodEvent.waitone();
    // do stuff...
}

public void execute()
{
    exeEvent.waitone();
    // do stuff...
}

public void writeBack()
{
    wbEvent.waitone();
    // do stuff...
}

和当前输出:

F           D           X           W
F      lda $50

F      sta $2        
            D      lda #$10    
F      lda #$0       
            D      sta $1        
                        X      sta $0        
            D      lda $50
                                    W      sta $0        
                        X      lda #$10    
F      sta $0        
F      lda #$10    
            D      lda $50
                        X      sta $1        
                                    W      sta $0        
F      sta $1        
                                    W      sta $0        
                        X      lda $50
            D      sta $2        
            D      lda #$0       
                        X      lda $50
                                    W      sta $0 

我想要的输出:

F           D           X           W
F      lda $10

F      sta $0        
            D      lda #$10    
F      lda #$11     
            D      sta $0        
                        X      lda #$10  
F      sta $0          
            D      lda $11     
                        X      sta $0    
                                    W      lda #$10      
F      lda #$10    
            D      sta $0
                        X      lda $11         
                                    W      sta $0        
F      sta $1        
            D      lda #$10 
                        X      sta $0 
                                    W      lda $11               
            D      sta $1
                        X      lda #$10
                                    W      sta $0 

我的格式是这样的。让我更容易看到订购。同样,这些打印语句中的每一个--F,D,X,W - 均由不同的线程触发。有关如何实现这一目标的任何意见?

我也愿意使用锁,如果有办法使用它我不知道。

4 个答案:

答案 0 :(得分:1)

原因是您一次设置所有事件。

一旦主线程被唤醒,它们被Windows调度的执行顺序是未定义的,并且最后一个线程可能比第一个线程更早开始。

查看生产者 - 消费者集合,例如BlockingCollection:http://msdn.microsoft.com/en-us/library/dd267312(v=vs.110).aspx

这些将允许您同时运行所有四个,消耗前一个产品并将输出发送到下一个产品。

您将问题描述为由每个输入元素触发的一系列顺序步骤。在处理块之间,您可能有一些其他类型的对象和不同数量的对象,但这仍然是生产者 - 消费者模式。

在这种情况下,生产者 - 消费者将应用于所有后续处理器对。他们正在积极运行,等待前任的输入并将输出发送给后继者。一旦你开始考虑如何管理资源来存储这些临时输出,你自然会想到排队。

绘制流程图,为每个数据流链接创建一个BlockingCollection,并在构造函数中为其提供一些队列以确保排序(如果需要)。另外,指定每个项目数量的限制,这将是您的“缓冲区大小”。消费者将使用“GetConsumingEnumerable”方法并在其上应用foreach - 当生产者队列中没有数据时,它将自动阻止。

完成后,重新检查每个生产者 - 消费者对,以确保它们平均以相同的速度运行。如果任何消费者的运行速度明显加快,请考虑将其代码合并到前任生产者中,因为在该链接上排队是无用的。

答案 1 :(得分:1)

以这种方式试试,

public void Button_OnClick() {
    nextInstruction();
}

public void nextInstruction() 
{
    fetchEvent.Set();
}

public void fetch() 
{
    while(true) {
        fetchEvent.waitone();
        // do stuff...
        decodeEvent.Set();
    }
}

public void decode()
{
    while(true) {
        decodEvent.waitone();
        // do stuff...
        executeEvent.Set();
    }
}

答案 2 :(得分:1)

注意!以下内容仅适用于.NET 4或更高版本。旧的.NET版本中既没有 Parallel 也没有 ConcurrentDictionary

使用Parallel.Invoke(),您可以同时执行您的操作,同时在所有操作完成之前不会返回此方法的优势。

使用类似集合或字典的数据类型,其中操作可以存储其(输出)结果,以便以后进行顺序处理。

在下面的示例代码中,我使用了ConcurrentDictionary。 ConcurrentDictionary的键是动作本身。如果要在其他地方处理此字典,将操作本身用作键是不明智的。在这种情况下,您应该实现一个公共枚举(表示操作)作为字典的键。

由于动作同时运行并且可能在同一时间访问字典,因此选择了线程安全的ConcurrentDictionary类型。 (普通字典不是线程安全的,因此可能会导致零星的,看似随机的错误。

public class InstructionCycles
{
    private readonly ConcurrentDictionary<Action, string> _dictActionResults = new ConcurrentDictionary<Action, string>();

    private void fetch()
    {
        // do something and store the result in the dictionary
        _dictActionResults[fetch] = "FetchResult";
    }

    private void decode()
    {
        // do something and store the result in the dictionary
        _dictActionResults[decode] = "DecodeResult";
    }

    private void execute()
    {
        // do something and store the result in the dictionary
        _dictActionResults[execute] = "ExecuteResult";
    }

    private void writeBack()
    {
        // do something and store the result in the dictionary
        _dictActionResults[writeBack] = "WriteBackResult";
    }


    public static void nextInstruction()
    {
        InstructionCycles instrCycles = new InstructionCycles();

        Action[] actions = 
        {
            instrCycles.fetch,
            instrCycles.decode,
            instrCycles.execute,
            instrCycles.writeBack
        };

        Parallel.Invoke(actions);

        // output the results in sequential order

        foreach (Action a in actions)
        {
            Console.Out.WriteLine(instrCycles._dictActionResults[a]);
        }
    }
}

通过调用InstructionCycles.nextInstruction()执行指令。

使用静态方法nextInstruction()(内部创建 InstructionCycles 的实例)允许在需要时并行运行多个指令,因为每条指令都使用自己的结果字典而不会干扰其他指令


如果不需要静态方法,并且也不要求并行执行指令,那么 nextInstruction()可以改为:

    private readonly object _lockObj = new object();

    public void nextInstruction()
    {
        Action[] actions = 
        {
            fetch,
            decode,
            execute,
            writeBack
        };

        lock (_lockObj)
        {
            _dictActionResults.Clear();

            Parallel.Invoke(actions);

            // output the results in sequential order

            foreach (Action a in actions)
            {
                Console.Out.WriteLine(_dictActionResults[a]);
            }
        }
    }

请注意lock声明。如果在任何情况下调用 instructionNext()而另一个线程已在执行 instructionNext(),则锁将阻止第二个线程的执行,直到第一个线程完成< EM> instructionNext()的。作为锁定对象,应该选择一个不能从类外部访问的私有对象,这将避免许多潜在的死锁情况。

答案 3 :(得分:1)

这是演示如何实现类似于我在第一个答案中给出的Parallel.Invoke()示例,但这次仅使用 AutoResetEvent

可以使用

ManualResetEvent 对象代替 AutoResetEvent ,但是在 nextInstruction()的情况下,代码需要注意重置这些对象方法。)

每个指令周期任务都会休眠一段任意时间(“模拟”不同的执行时间),以便演示AutoResetEvents确保输出结果的正确顺序的效果。

在等待输出结果的权限之前,所有周期将同时运行。 只有在相应的AutoResetEvent上调用 WaitOne()方法(等待输出结果的权限),其余任务(输出结果)才会按顺序执行。

public class InstructionCycles
{
    private readonly AutoResetEvent DecodeAllowedToOutputEvent = new AutoResetEvent(false);
    private readonly AutoResetEvent ExecuteAllowedToOutputEvent = new AutoResetEvent(false);
    private readonly AutoResetEvent WriteBackAllowedToOutputEvent = new AutoResetEvent(false);

    // 
    // The InstructionFinishedEvent would not be necessary,
    // if nextInstruction() does not need to wait for the instruction to finish.
    //
    AutoResetEvent InstructionFinishedEvent = new AutoResetEvent(false);

    private void fetch()
    {
        try
        {
            // do something useful...
            // For demo purpose, lets just sleep some arbitrary time
            Thread.Sleep(500);

            // This is the 1st cycle.
            // So we don't need to wait for a previous cycle outputting its result.
            Console.Out.WriteLine("FetchResult");
        }
        finally
        {
            // Allow the next cycle to output its results...
            DecodeAllowedToOutputEvent.Set();
        }
    }

    private void decode()
    {
        try
        {
            // do something useful...
            // For demo purpose, lets just sleep some arbitrary time
            Thread.Sleep(200);

            // Processing done.
            // Now wait to be allowed to output the result.
            DecodeAllowedToOutputEvent.WaitOne();

            Console.Out.WriteLine("DecodeResult");
        }
        finally
        {
            // Allow the next cycle to output its results...
            ExecuteAllowedToOutputEvent.Set();
        }
    }

    private void execute()
    {
        try
        {
            // do something useful...
            // For demo purpose, lets just sleep some arbitrary time
            Thread.Sleep(300);

            // Processing done.
            // Now wait to be allowed to output the result.
            ExecuteAllowedToOutputEvent.WaitOne();

            Console.Out.WriteLine("ExecuteResult");
        }
        finally
        {
            // Allow the next cycle to output its results...
            WriteBackAllowedToOutputEvent.Set();
        }
    }

    private void writeBack()
    {
        try
        {
            // do something useful...
            // For demo purpose, lets just sleep some arbitrary time
            Thread.Sleep(100);

            // Processing done.
            // Now wait to be allowed to output the result.
            WriteBackAllowedToOutputEvent.WaitOne();

            Console.Out.WriteLine("WriteBackResult");
        }
        finally
        {
            // Signal that the instruction (including outputting the result) has finished....
            InstructionFinishedEvent.Set();
        }
    }


    public void nextInstruction()
    {
        //
        // The order in which the cycles are started doesn't really matter,
        // since the way how the AutoResetEvents are being used will ensure
        // correct sequence of outputting results.
        //

        Task.Factory.StartNew(fetch);
        Task.Factory.StartNew(decode);
        Task.Factory.StartNew(execute);
        Task.Factory.StartNew(writeBack);

        // 
        // The InstructionFinishedEvent would not be necessary,
        // if nextInstruction() does not need to wait for the instruction to finish.
        //
        InstructionFinishedEvent.WaitOne();
    }
}

通过利用try {...} finally {...},即使其中一个线程中的某些代码决定抛出异常,也确保事件链不受阻碍地工作。

即使您敢于故意处置一个或多个AutoResetEvents然后调用 nextInstruction(),指令周期的执行仍将同时发生。但是,结果的输出将不再按预期发生,因为当其中一个循环尝试等待已处置且现在无效的AutoResetEvent时,将抛出异常,并且将向下一个线程发出输出其结果的权限(感谢到try-finally块的工作方式。)

注意:乍一看,代码可能与好奇的答案类似。但是,程序流/事件处理方面存在差异,这些差异对整个流程的行为很重要。

另请注意,上面给出的 nextInstruction()本身并不是线程安全的。要允许来自不同调用线程的多个 nextInstruction()调用,就像在我的其他答案中一样,必须使用lock来确保一次只执行一条指令:

    private readonly object _lockObj = new object();

    public void nextInstruction()
    {
        //
        // The order in which the cycles are started doesn't really matter,
        // since the way how the AutoResetEvents are being used will ensure
        // correct sequence of outputting results.
        //

        lock (_lockObj)
        {
            Task.Factory.StartNew(fetch);
            Task.Factory.StartNew(decode);
            Task.Factory.StartNew(execute);
            Task.Factory.StartNew(writeBack);

            // 
            // When using lock, InstructionFinishedEvent must
            // be used to ensure that nextInstance() remains
            // in the lock until the instruction finishes.
            //
            InstructionFinishedEvent.WaitOne();
        }
    }