2

我目前正在为我的 asm 模拟器创建一个管道。我要用多线程来做到这一点。目前我有四个线程,分别对应于 Fetch、Decode、Execute 和 Write Back。我对如何编写正确的管道感到困惑。这是我的一些代码:

private void Fetch()
{
    while(_continue)
    {
        fetchEvent.WaitOne();
        if (!_continue) break;
        lock (irLock)
        {
            try
            {
                // Do stuff to fetch instruction...

                catch (IndexOutOfRangeException e) { Err = "Segmentation Fault at Line " + _pc.ToString(); }
                catch (Exception e) { Err = e.Message + "Line " + _pc.ToString(); }
                _pc++;

                GiveTurnTo(2); // used these 2 lines to handle mutual exclusion
                WaitTurn(1);
            }
        }
    }

    private void Decode()
    {
        while (_continue)
        {
            decodeEvent.WaitOne();
            if (!_continue) break;

            lock (irLock)
            {
                WaitTurn(2);
                Console.WriteLine("decode works");
                GiveTurnTo(1);
            }


        }
    }

    private void Execute()
    {
        while (_continue)
        {
            exeEvent.WaitOne();
            if (!_continue) break;

            lock (irLock)
            {
                //WaitTurn(3);
                Console.WriteLine("Am I, execute, getting a turn?");
                // GiveTurnTo(4);
            }

        }

    }

    private void WriteBack()
    {
        while (_continue)
        {
            wbEvent.WaitOne();
            if (!_continue) break;

            lock (irLock)
            {
                Console.WriteLine("Am I, Write Back, getting a turn?");
                //GiveTurnTo(1);
                // WaitTurn(4);
            }

        }

    }
}

我使用此方法通过单击按钮运行循环:

public void nextInstruction()
{
    fetchEvent.Set();
    decodeEvent.Set();
    exeEvent.Set();
    wbEvent.Set();
}
I was thinking of changing nextInstruction() to this:

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

每个按钮单击将始终以 Fetch 开头。在那之后,我想也许我可以在 Fetch 方法中放置一个 set 事件来调用循环中的下一部分(解码)并对以下方法执行相同的操作。我最终会得到这样的结果:

private void Fetch()
{
    while(_continue)
    {
        // Do stuff....
        decodeEvent.Set();
    }
}

private void Decode()
{
    while (_continue)
    {
        // Do stuff...
        exeEvent.Set();
    }
}

private void Execute()
{
    while (_continue)
    {
        // Do stuff...
        wbEvent.Set();
    }
}
private void WriteBack()
{
    while (_continue)
    {
        // Do stuff...
    }
}

与其把这些设定的事件公开,我认为它们应该被某种逻辑激活,但我不知道是什么样的逻辑。也许它们可以通过单击按钮来激活。这将允许我控制指令何时在循环中传递。从理论上讲,我认为这可以给我一个管道的结构。任何人都可以对此提供任何意见吗?这可以通过自动重置事件来完成吗?

如果您查看我提供的第一段代码,您会发现我尝试使用锁,但这使得它一次只能运行一个线程。我想让它遵循等的格式Fetch0, {Decode0, Fetch1}, {Execute0, Decode1, Fetch3},...。在这种情况下,锁是必需品吗?

4

2 回答 2

1

线程不适合这个。在使线程进入睡眠状态之前,您每一步所做的工作太少。模拟它的更好方法是让单个线程以相反的顺序执行每个步骤,这将像真实系统一样将工作波及系统。

public void nextInstruction()
{
    WriteBack();
    Execute();
    Decode();
    Fetch();
}

为了帮助理解我为什么以相反的顺序处理它们,看看我的系统是如何工作的,因为我们反复调用它。

  • 呼叫 1
    1. WriteBack()什么都不做,它的输入缓冲区是空的。
    2. Execute()什么都不做,它的输入缓冲区是空的。
    3. Decode()什么都不做,它的输入缓冲区是空的。
    4. Fetech()抓取第一条指令并将其放入Decode()的输入缓冲区。
  • 呼叫 2
    1. WriteBack()什么都不做,它的输入缓冲区是空的。
    2. Execute()什么都不做,它的输入缓冲区是空的。
    3. Decode()从其输入缓冲区中获取获取的第一条指令,将解码后的指令放入Execute()的输入缓冲区。
    4. Fetech()抓取第二条指令并将其放入Decode()的输入缓冲区。
  • 呼叫 3
    1. WriteBack()什么都不做,它的输入缓冲区是空的。
    2. Execute()从其输入缓冲区中获取解码后的第一WriteBack()条指令,对其进行处理,然后将结果放入的输入缓冲区。
    3. Decode()从其输入缓冲区中获取第二Execute()条指令,对其进行处理,然后将解码后的指令放入的输入缓冲区。
    4. Fetech()抓取第3条指令并将其放入Decode()的输入缓冲区。
  • 呼叫 4
    1. WriteBack()从其输入缓冲区中获取处理后的第一个值并将其写回内存存储。
    2. Execute()从其输入缓冲区中获取解码后的第二WriteBack()条指令,对其进行处理,然后将结果放入的输入缓冲区。
    3. Decode()从其输入缓冲区中获取获取的第三Execute()条指令,对其进行处理,然后将解码后的指令放入的输入缓冲区。
    4. Fetech()抓取第4条指令并将其放入Decode()的输入缓冲区。
  • 呼叫 5
    1. WriteBack()从其输入缓冲区中获取处理后的第二个值并将其写回内存存储区。
    2. Execute()从其输入缓冲区中获取解码后的第 3WriteBack()条指令,对其进行处理,然后将结果放入的输入缓冲区。
    3. Decode()从其输入缓冲区中获取第 4 条指令,对其进行处理,然后将解码后的指令放入的输入Execute()缓冲区。
    4. Fetech()抓取第5条指令并将其放入Decode()的输入缓冲区。
  • 呼叫 6
    1. WriteBack()从其输入缓冲区中获取处理后的第三个值并将其写回内存存储。
    2. Execute()从其输入缓冲区中获取解码后的第 4WriteBack()条指令,对其进行处理,然后将结果放入的输入缓冲区。
    3. Decode()从其输入缓冲区中获取获取的第 5 条指令,对其进行处理,然后将解码后的指令放入的输入Execute()缓冲区。
    4. Fetech()抓取第6条指令并将其放入Decode()的输入缓冲区。
  • 等等。

看到在流水线满之前需要 4 次调用,这就是现实生活中发生的情况,将第一条指令从Fetch到需要更多的周期,WriteBack但是相比之下,从流水线中出来的指令之间的时间间隔要短得多。

现在看看你的旧系统,你会发现你实际上从来没有填满你的管道,一条指令可以在一个模拟步骤中直接通过所有阶段。

  • 呼叫 1
    1. Fetech()抓取第一条指令并将其放入Decode()的输入缓冲区。
    2. Decode()从其输入缓冲区中抓取第一Execute()条指令,对其进行处理,然后将解码后的指令放入的输入缓冲区。
    3. Execute()从其输入缓冲区中获取解码后的第一WriteBack()条指令,对其进行处理,然后将结果放入的输入缓冲区。
    4. WriteBack()从其输入缓冲区中获取处理后的第一个值并将其写回内存存储。
  • 呼叫 2
    1. Fetech()抓取第二条指令并将其放入Decode()的输入缓冲区。
    2. Decode()从其输入缓冲区中获取第二Execute()条指令,对其进行处理,然后将解码后的指令放入的输入缓冲区。
    3. Execute()从其输入缓冲区中获取解码后的第二WriteBack()条指令,对其进行处理,然后将结果放入的输入缓冲区。
    4. WriteBack()从其输入缓冲区中获取处理后的第二个值并将其写回内存存储区。

在一次调用中,我们的指令在我们甚至可以获取第二条指令之前就从获取到写回

于 2013-11-16T04:20:32.570 回答
1

管道是引发事件同步或异步的过程。观察者在管道中注册,以便收到这些事件的通知。管道存储状态以允许观察者使用状态。在异步事件的情况下,您必须确保围绕状态进行同步,以及回调以了解线程何时完成或准备好工作。这是使用管道的粗略高级示例:

public class OnFetchEvent : AsyncEvent {}
public class OnDecodeEvent : AsyncEvent {}
public class OnExecuteEvent : AsyncEvent {}
public class OnWritebackEvent : AsyncEvent {}

public class FetchObserver : Observer,
   IObserve<OnFetchEvent>
{
   public void OnEvent(OnFetchEvent @event)
   {
      ....do some stuff

      // raise the next event
      RaiseEvent<OnDecodeEvent>(); 
   }
}

public class Pipeline
{
   public void RaiseEvent<TEvent>()
   {
      if (typeof(TEvent) is AsyncEvent)
        ...create thread and raise the event which will notify the appropriate 
           observers of the event in the newly created thread
   }

  }

用法:

pipeline.RegisterObserver<FetchObserver>()
  .AndObserver<DecodeObserver>()
  .AndObserver<ExecuteObserver>()
  .AndObserver<WriteBackObserver>();

pipeline.RaiseEvent<OnFetchEvent>();

这将处理一个周期。事件是异步引发的(这意味着观察者将在每个线程中执行)。要拥有多个周期,您需要启动管道异步(它自己的线程)。并根据需要调用管道 RaiseEvent。我希望这提供了一些使用线程来使用管道的高级方法。

于 2013-11-16T08:55:51.783 回答