协同程序终止

运行过程

为了更好地理解协同程序,我们将研究基本过程的语义 run .

run start_network;

这个 run 过程是一个普通的过程,当它完成时返回。它创造了一个新的 协同程序调度程序 对象,然后在该调度程序上生成其参数。然后它运行调度程序上的所有光纤,直到没有剩余的光纤,然后返回。

纤维状态

每根纤维都处于四种状态之一。要么就是 运行积极的等待dead .

在任何时候,对于任何调度程序,只有一个运行光纤。如果没有正在运行的光纤,调度程序将终止。每个自动生成的pthread都运行一个fibre调度器,包括主线线程!

纤维是 积极的 如果它暂停但准备运行。调度程序保留活动光纤的列表。当当前正在运行的光纤挂起时,调度程序只从活动列表中选择另一个光纤并运行它。如果活动列表中没有光纤,则调度程序将返回。

正在等待的光纤将是 等待阅读正在等待写入 . 等待总是发生在特定的schannel上。因此,每一个夏内尔都是 空的 ,包含等待读取的纤程列表,或包含等待写入的纤程列表。

如果在空的通道上执行写入操作,写入光纤将添加到通道等待列表中。如果通道中已经包含等待写入的光纤,则写入光纤也会添加到通道等待列表中。

但是,如果通道包含等待读取的光纤列表,则其中一个光纤将从通道等待列表中移除,正在写入的对象将从写入器传输到读取器,两个光纤都将添加到调度器活动列表中。由于编写器正在运行,现在只是处于活动状态,所以调度程序选择另一个光纤来运行。

读操作的工作方式完全相同,交换意思或读写。

可达性

如果一个光纤正在等待读取,但没有其他光纤写入它正在等待的通道,则称该光纤正在等待 饥肠辘辘 . 如果一个光纤正在等待写入,但没有其他光纤从它正在等待的通道中进行读取,则该光纤被称为 此路不通 .

对于pthreads,这种锁定是致命的,并且总是设计错误。纤维可不是这样!回忆一下我们的读者:

// reader fibre
proc reader (inp: %<int) ()
{
  while true do
    var x = read inp;
    println$ "Read " + x.str;
  done
}

这是一个无限循环,但我们的write只写了10个整数。所以在读了10个整数之后,我们的纤维会饿死!

这就是诀窍!只有读写器光纤才能到达连接它们的通道。没有人可以在那个频道上写,因为没有人知道它的名字。更重要的是,作者已经回来了,所以现在已经死了,所以它也无法到达频道,因为它不存在!

因此,由于读卡器没有运行或处于活动状态,甚至调度器也不知道。只有频道知道,只有读者知道频道。

所以调度程序现在没有正在运行或活动的过程,它返回。饥肠辘辘的读者和频道是无法到达和被遗忘的。所以垃圾回收器只是删除它们。

与pthreads不同,锁定可能是 对的 终止的方式。这句格言是,光纤不能死锁,因为如果它们死了,它们就死了;如果它们死了,就不存在,所以它们不能死锁。

然而,协同程序可以 活锁 . 如果有一个光纤可以在一个通道上写入,这将缓解另一个光纤的不足,但选择了不写入,或者,一个可以从一个通道中读取数据的光纤可以缓解阻塞,但不选择太多,则会发生livelock。在其他作品中,通道是 静态可达 但是是 动态忽略 .

正确设计协同程序的关键很简单:如果一个例程不打算在一个通道上执行I/O,它就必须忘记它。换句话说,频道必须超出范围。

我们的 start_network 程序遵守这条规则。它从不从它所构造的通道中读写,但是在将通道绑定到读写过程并将绑定作为纤程生成之后,它会返回,从而忘记它对通道的知识,特别是因为它在返回后不存在!