代码之家  ›  专栏  ›  技术社区  ›  Oded

我只是没法继续!

  •  32
  • Oded  · 技术社区  · 18 年前

    它们是什么?它们有什么用?

    我没有CS学位,我的背景是vb6->asp->asp.net/c。有人能用一种简洁明了的方式来解释吗?

    9 回复  |  直到 17 年前
        1
  •  41
  •   Andreas Grech    17 年前

    想象一下,如果程序中的每一行都是单独的函数。每个函数都接受下一行/下一个要执行的函数作为参数。

    使用这个模型,您可以在任何一行“暂停”执行,稍后再继续执行。您还可以做一些创造性的事情,例如临时跳上执行堆栈以检索值,或者将当前执行状态保存到数据库以供以后检索。

        2
  •  11
  •   Dustin    17 年前

    你可能比你想象的更了解他们。

    例外是“仅向上”延续的一个例子。它们允许代码深入堆栈调用异常处理程序来指示问题。

    Python示例:

    try:
        broken_function()
    except SomeException:
        # jump to here
        pass
    
    def broken_function():
        raise SomeException() # go back up the stack
        # stuff that won't be evaluated
    

    生成器是“仅向下”延续的示例。例如,它们允许代码重新输入一个循环来创建新的值。

    Python示例:

    def sequence_generator(i=1):
        while True:
            yield i  # "return" this value, and come back here for the next
            i = i + 1
    
    g = sequence_generator()
    while True:
        print g.next()
    

    在这两种情况下,它们都必须被特别地添加到语言中,而在具有连续性的语言中,程序员可以在它们不可用的地方创建这些东西。

        3
  •  9
  •   Kyle Cronin    18 年前

    A抬头一看,这个例子既不简洁也不特别清楚。这是一个强大的延续应用程序的演示。作为一个VB/ASP/C程序员,您可能不熟悉系统堆栈或保存状态的概念,所以这个答案的目标是演示而不是解释。

    延续非常通用,是保存执行状态并稍后恢复的一种方法。下面是一个在Scheme中使用continuations的协作多线程环境的小例子:

    (假设在未在此处定义的全局队列上,排队和出列操作按预期工作)

    (define (fork)
      (display "forking\n")
      (call-with-current-continuation
       (lambda (cc)
         (enqueue (lambda ()
                    (cc #f)))
         (cc #t))))
    
    (define (context-switch)
      (display "context switching\n")
      (call-with-current-continuation
       (lambda (cc)
         (enqueue
          (lambda ()
            (cc 'nothing)))
         ((dequeue)))))
    
    (define (end-process)
      (display "ending process\n")
      (let ((proc (dequeue)))
        (if (eq? proc 'queue-empty)
            (display "all processes terminated\n")
            (proc))))
    

    这提供了一个函数可以使用的三个动词-fork、context switch和end process。fork操作分叉线程,并在一个实例中返回t,在另一个实例中返回f。上下文切换操作在线程之间切换,结束进程终止线程。

    以下是它们的使用示例:

    (define (test-cs)
      (display "entering test\n")
      (cond
        ((fork) (cond
                  ((fork) (display "process 1\n")
                          (context-switch)
                          (display "process 1 again\n"))
                  (else (display "process 2\n")
                        (end-process)
                        (display "you shouldn't see this (2)"))))
        (else (cond ((fork) (display "process 3\n")
                            (display "process 3 again\n")
                            (context-switch))
                    (else (display "process 4\n")))))
      (context-switch)
      (display "ending process\n")
      (end-process)
      (display "process ended (should only see this once)\n"))
    

    输出应该是

    entering test
    forking
    forking
    process 1
    context switching
    forking
    process 3
    process 3 again
    context switching
    process 2
    ending process
    process 1 again
    context switching
    process 4
    context switching
    context switching
    ending process
    ending process
    ending process
    ending process
    ending process
    ending process
    all processes terminated
    process ended (should only see this once)
    

    在一个类中学习过分叉和线程的人通常会得到类似的例子。本文的目的是证明通过继续,您可以通过手动保存和恢复单个线程的状态(继续),在单个线程内实现类似的结果。

    另外。-我想我在Lisp上还记得类似的东西,所以如果你想看专业代码,你应该看看这本书。

        4
  •  7
  •   Paul Johnson    17 年前

    认为延续的一种方法是作为处理器堆栈。当您“用当前的延续c调用”时,它调用您的函数“c”,并且传递给“c”的参数是您当前的堆栈,上面有您所有的自动变量(作为另一个函数表示,称之为“k”)。同时,处理器开始创建新的堆栈。当您调用“k”时,它会在原始堆栈上执行“从子例程返回”(RTS)指令,将您跳转回原始“当前继续调用”(“从现在起调用cc”)的上下文,并允许您的程序像以前一样继续。如果您将参数传递给“k”,那么这将成为“call cc”的返回值。

    从原始堆栈的角度来看,“call cc”看起来像正常的函数调用。从“c”的角度来看,原始堆栈看起来像一个永远不会返回的函数。

    有一个古老的笑话,讲的是一个数学家爬进笼子,把它锁起来,然后宣称自己在笼子外面,而其他一切(包括狮子)都在笼子里面,从而把一头狮子抓进笼子里。延续有点像笼子,“c”有点像数学家。你的主程序认为“c”在里面,“c”认为你的主程序在“k”里面。

    您可以使用continuations创建任意的控制结构流。例如,您可以创建线程库。”yield“使用”call cc“将当前的延续放在队列上,然后跳到队列头上的延续。信号量也有它自己的暂停延续队列,线程通过将其从信号量队列中取出并放到主队列中来重新调度。

        5
  •  4
  •   Jason Baker    18 年前

    基本上,延续是一个函数停止执行,然后在稍后某个时间点从停止的地方恢复的能力。在C中,您可以使用yield关键字来执行此操作。如果你愿意的话,我可以更详细地讲,但你需要一个简明的解释。;-)

        6
  •  2
  •   user487329    17 年前

    我仍在“习惯”延续,但我认为有一种方法可以用来思考它们,那就是将程序计数器(PC)概念抽象化。一台PC机“指向”下一条要在内存中执行的指令,但当然,指令(以及几乎所有的指令)隐式或显式地指向后面的指令,以及服务中断的任何指令。(即使noop指令也会隐式跳转到内存中的下一条指令。但如果发生中断,通常需要跳转到内存中的其他指令。)

    在某种意义上,程序内存中的每个潜在的“活动”点(控件可能在任何给定点跳转到该点)都是活动的延续。可以达到的其他点是潜在的活动延续,但更确切地说,它们是由于达到一个或多个当前活动延续而潜在“计算”的延续(可能是动态的)。

    在传统的连续性介绍中,这似乎有点不合时宜,其中所有挂起的执行线程都显式地表示为静态代码的连续性;但它考虑到了这样一个事实:在通用计算机上,PC指向的指令序列可能会改变内存表示的内容。重新发送该指令序列的一部分,从而实质上创建了一个新的(或修改过的,如果您愿意的话)动态延续,这个延续在创建/修改之前的延续激活中并不存在。

    因此,延续可以被视为PC的高级模型,这就是为什么它在概念上包含普通的过程调用/返回(就像古代的Iron一样,通过低级跳转进行过程调用/返回,又称Goto、指令加上对PC调用的记录和返回时的恢复)以及异常、线程、协程等。

    因此,正如PC指出“未来”将要发生的计算一样,延续也会做同样的事情,但在更高、更抽象的层次上。PC隐式地引用内存加上所有内存位置和寄存器“绑定”到任何值,而延续则通过语言适当的抽象来表示未来。

    当然,虽然每台计算机(核心处理器)通常只有一台PC,但实际上有许多“活跃的”PC-ish实体,正如上面提到的那样。中断向量包含一束,堆栈更多,某些寄存器可能包含一些,等等。当它们的值被加载到硬件PC时,它们被“激活”,但是连续性是概念的抽象,而不是PC或它们的精确等价物(尽管我们经常认为和用这些术语编写代码,使事情相当简单)。

    本质上,延续是“当被调用时下一步要做什么”的一种表示,因此,可以是(并且,在某些语言和延续传递式程序中,通常是)一个被实例化、传递和丢弃的一级对象,就像大多数其他数据类型一样,非常类似于经典计算机如何处理内存位置。 视界 PC——几乎可以与普通整数互换。

        7
  •  2
  •   Rich Dougherty    17 年前

    在C中,您可以访问两个延续。一个,通过 return ,让方法从调用位置继续。另一个通过 throw ,让一个方法在最近的匹配处继续 catch .

    有些语言允许您将这些语句视为第一类值,因此您可以分配它们并在变量中传递它们。这意味着你可以储存 返回 或 扔 以后再给他们打电话 真的? 准备返回或抛出。

    Continuation callback = return;
    callMeLater(callback);
    

    这在很多情况下都很方便。有一个例子与上面的例子类似,你想暂停你正在做的工作,然后在发生某些事情时(比如收到一个Web请求,或者其他事情)继续它。

    我在几个项目中使用它们。在一个例子中,我使用它们,这样我就可以在网络上等待IO时暂停程序,然后稍后再继续。在另一种语言中,我编写了一种编程语言,在这种语言中,我允许用户访问作为值的延续,这样他们就可以编写 返回 和 扔 对于他们自己或任何其他控制流,比如 while 循环-不需要我为他们做。

        8
  •  1
  •   Jules    17 年前

    想想线程。线程可以运行,您可以得到它的计算结果。延续是可以复制的线程,因此可以运行相同的计算两次。

        9
  •  1
  •   ja.    17 年前

    连续性重新引起了Web编程的兴趣,因为它们很好地反映了Web请求的暂停/恢复特性。服务器可以构造表示用户会话的Continuation,并在用户继续会话时继续。

    推荐文章