2008-09-02 22 views

Trả lời

41

Hãy tưởng tượng nếu mỗi dòng trong chương trình của bạn là một chức năng riêng biệt. Mỗi chấp nhận, như một tham số, dòng/hàm tiếp theo để thực thi.

Sử dụng mô hình này, bạn có thể "tạm dừng" thực thi tại bất kỳ dòng nào và tiếp tục sau đó. Bạn cũng có thể làm những điều sáng tạo như tạm thời nhảy lên ngăn xếp thực hiện để lấy một giá trị hoặc lưu trạng thái thực thi hiện tại vào cơ sở dữ liệu để truy xuất sau này.

+0

wow, điều đó thực sự có ý nghĩa. cảm ơn! – minimalpop

+0

Có, giải thích rất tốt! 1 từ tôi;) – Alfred

+4

Tôi cũng thích nó, nhưng phần nào chính xác là sự tiếp tục? – user2023370

9

Thủ trưởng, ví dụ này không súc tích hoặc không rõ ràng. Đây là một minh chứng cho một ứng dụng mạnh mẽ của sự tiếp tục. Là một lập trình viên VB/ASP/C#, bạn có thể không quen thuộc với khái niệm về ngăn xếp hệ thống hoặc trạng thái lưu trữ, vì vậy mục tiêu của câu trả lời này là một minh chứng và không phải là giải thích.

Tiếp tục cực kỳ linh hoạt và là cách để lưu trạng thái thực thi và tiếp tục lại sau. Dưới đây là một ví dụ nhỏ của một môi trường đa luồng hợp tác xã sử dụng continuations trong Đề án:

(Giả sử rằng các hoạt động enqueue và làm việc dequeue như mong đợi trên một hàng đợi toàn cầu không được định nghĩa ở đây)

(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)))) 

này cung cấp ba động từ đó một hàm có thể sử dụng - fork, context-switch và end-process. Các hoạt động ngã ba dĩa các chủ đề và trả về # t trong một ví dụ và #f trong khác. Thao tác chuyển ngữ cảnh chuyển đổi giữa các luồng và quá trình kết thúc sẽ chấm dứt một luồng.

Dưới đây là một ví dụ về việc sử dụng chúng:

(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")) 

Sản lượng nê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) 

Những người đã nghiên cứu forking và luồng trong một lớp học thường được ví dụ tương tự như sau. Mục đích của bài viết này là để chứng minh rằng với việc tiếp tục bạn có thể đạt được kết quả tương tự trong một luồng đơn bằng cách lưu và khôi phục trạng thái của nó - sự tiếp tục của nó - theo cách thủ công.

P.S. - Tôi nghĩ rằng tôi nhớ một cái gì đó tương tự như thế này trong On Lisp, vì vậy nếu bạn muốn xem mã chuyên nghiệp, bạn nên kiểm tra cuốn sách ra.

4

Về cơ bản, việc tiếp tục là khả năng cho một chức năng dừng thực thi và sau đó chọn lại nơi nó đã dừng lại tại một thời điểm sau đó. Trong C#, bạn có thể làm điều này bằng cách sử dụng từ khóa lợi nhuận. Tôi có thể đi vào chi tiết hơn nếu bạn muốn, nhưng bạn muốn có một lời giải thích ngắn gọn. ;-)

2

Tôi vẫn đang "sử dụng" để tiếp tục, nhưng một cách để suy nghĩ về chúng mà tôi thấy hữu ích là trừu tượng của khái niệm Bộ đếm chương trình (PC). Một PC "trỏ" tới lệnh tiếp theo để thực hiện trong bộ nhớ, nhưng tất nhiên chỉ lệnh đó (và khá nhiều mọi lệnh) điểm, ngầm hoặc rõ ràng, theo hướng dẫn sau, cũng như bất kỳ hướng dẫn nào nên ngắt dịch vụ. (Ngay cả một lệnh NOOP ngầm thực hiện một JUMP đến lệnh tiếp theo trong bộ nhớ. Nhưng nếu một ngắt xảy ra, thì thường sẽ liên quan đến một JUMP đến một số lệnh khác trong bộ nhớ.)

Mỗi điểm "sống" có khả năng trong một chương trình trong bộ nhớ mà kiểm soát có thể nhảy tại bất kỳ điểm nào, theo nghĩa nào đó, một sự tiếp tục hoạt động.Các điểm khác có thể đạt được là tiếp tục có khả năng hoạt động, nhưng, nhiều hơn đến điểm, chúng là các tiếp nối có khả năng "được tính toán" (động, có lẽ) là kết quả của việc tiếp cận một hoặc nhiều tiếp tục hoạt động hiện tại.

Điều này có vẻ hơi lệch vị trí trong phần giới thiệu truyền thống để tiếp tục, trong đó tất cả các chuỗi đang chờ xử lý được biểu diễn rõ ràng dưới dạng tiếp nối thành mã tĩnh; nhưng nó có tính đến thực tế rằng, trên các máy tính có mục đích chung, PC chỉ vào một chuỗi lệnh có khả năng thay đổi nội dung của bộ nhớ đại diện cho một phần của chuỗi lệnh đó, do đó, về cơ bản tạo mới (hoặc sửa đổi, nếu bạn sẽ) tiếp tục trên bay, một trong đó không thực sự tồn tại như của các hoạt động tiếp tục trước đó tạo/sửa đổi.

Vì vậy, tiếp tục có thể được xem như là một mô hình cấp cao của PC, đó là lý do tại sao khái niệm này cho phép gọi/trả lại thủ tục thông thường (như sắt cổ xưa gọi thủ tục/trả về thông qua JUMP cấp thấp, còn gọi là GOTO) cộng với ghi âm của PC trên gọi và khôi phục lại nó trên trở lại) cũng như trường hợp ngoại lệ, chủ đề, coroutines, vv

Vì vậy, cũng giống như PC chỉ đến tính toán xảy ra trong "tương lai", tiếp tục thực hiện tương tự nhưng ở mức độ cao hơn, trừu tượng hơn. PC ngầm ám chỉ đến bộ nhớ cộng với tất cả các vị trí bộ nhớ và đăng ký "bị ràng buộc" với bất kỳ giá trị nào, trong khi sự tiếp tục biểu thị tương lai thông qua các trừu tượng thích hợp về ngôn ngữ.

Tất nhiên, mặc dù thường chỉ có một máy tính trên mỗi máy tính (bộ xử lý lõi), thực tế có nhiều thực thể PC-ish "hoạt động", như đã nói ở trên. Các vector ngắt có chứa một bó, ngăn xếp nhiều hơn, các thanh ghi nhất định có thể chứa một số, vv Chúng được "kích hoạt" khi các giá trị của chúng được nạp vào PC phần cứng, nhưng sự tiếp tục là trừu tượng của khái niệm, chứ không phải PC hoặc tương đương chính xác của chúng (không có khái niệm cố hữu về sự tiếp tục "chính", mặc dù chúng ta thường nghĩ và viết mã trong các thuật ngữ đó để giữ cho mọi thứ khá đơn giản). Về bản chất, sự tiếp tục là một biểu diễn của "những gì cần làm tiếp theo khi được gọi", và, như vậy, có thể (và, trong một số ngôn ngữ và trong các chương trình theo kiểu chuyển tiếp, thường là) đối tượng lớp được khởi tạo, được truyền xung quanh và loại bỏ giống như hầu hết các loại dữ liệu khác và giống như cách máy tính cổ điển xử lý các vị trí bộ nhớ vis-a-vis PC - gần như có thể hoán đổi với các số nguyên thông thường.

11

Bạn có thể hiểu chúng tốt hơn bạn nghĩ.

Trường hợp ngoại lệ là ví dụ về tiếp tục "chỉ lên". Chúng cho phép mã sâu xuống ngăn xếp để gọi tới một trình xử lý ngoại lệ để chỉ ra một vấn đề.

Python dụ:

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 

Máy phát điện là ví dụ về "xuống chỉ" continuations. Chúng cho phép mã để nhập lại một vòng lặp, ví dụ, để tạo ra các giá trị mới.

Python dụ:

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() 

Trong cả hai trường hợp, những phải được bổ sung vào ngôn ngữ đặc biệt trong khi ở một ngôn ngữ với sự tiếp tục, các lập trình viên có thể tạo ra những điều mà họ không có sẵn.

7

Một cách để nghĩ về sự tiếp tục là ngăn xếp bộ xử lý.Khi bạn "call-with-current-continuation c", nó gọi hàm của bạn là "c", và tham số được chuyển tới "c" là stack hiện tại của bạn với tất cả các biến tự động của bạn trên nó (được biểu diễn như một hàm khác, gọi nó là "k" "). Trong khi đó, bộ xử lý bắt đầu tạo một ngăn xếp mới. Khi bạn gọi "k", nó thực hiện lệnh "trở về từ chương trình con" (RTS) trên ngăn xếp ban đầu, nhảy bạn trở lại ngữ cảnh của "cuộc gọi-tiếp-hiện-tiếp" ban đầu ("gọi-cc" từ bây giờ bật) và cho phép chương trình của bạn tiếp tục như trước. Nếu bạn chuyển một tham số đến "k" thì giá trị này sẽ trở thành giá trị trả về của "call-cc".

Từ quan điểm của chồng ban đầu của bạn, "call-cc" trông giống như một cuộc gọi chức năng bình thường. Từ quan điểm của "c", ngăn xếp ban đầu của bạn trông giống như một hàm không bao giờ trả về.

Có một trò đùa cũ về một nhà toán học đã bắt một con sư tử trong lồng bằng cách leo vào lồng, khóa và tuyên bố mình ở bên ngoài lồng trong khi mọi thứ khác (kể cả sư tử) ở trong đó. Tiếp tục là một chút giống như lồng, và "c" là một chút giống như các nhà toán học. Chương trình chính của bạn nghĩ rằng "c" nằm bên trong nó, trong khi "c" tin rằng chương trình chính của bạn nằm trong "k".

Bạn có thể tạo cấu trúc kiểm soát dòng chảy tùy ý bằng cách tiếp tục. Ví dụ bạn có thể tạo một thư viện luồng. "yield" sử dụng "call-cc" để đặt sự tiếp tục hiện tại trên hàng đợi và sau đó nhảy vào hàng đầu trên hàng đợi. Một semaphore cũng có hàng đợi riêng của nó về việc tiếp tục đình chỉ, và một chuỗi được lên lịch lại bằng cách lấy nó ra khỏi hàng đợi semaphore và đưa nó vào hàng đợi chính.

2

Trong C#, bạn có quyền truy cập vào hai lần tiếp tục. Một, truy cập thông qua return, cho phép một phương pháp tiếp tục từ nơi nó được gọi. Người kia, được truy cập thông qua throw, cho phép phương pháp tiếp tục ở đối sánh gần nhất catch.

Một số ngôn ngữ cho phép bạn coi các câu lệnh này là giá trị hạng nhất, vì vậy bạn có thể gán chúng và chuyển chúng vào các biến. Điều này có nghĩa là bạn có thể stash giá trị của return hoặc của throw và gọi cho họ sau khi bạn đang thực sự sẵn sàng trả lại hoặc ném.

Continuation callback = return; 
callMeLater(callback); 

Điều này có thể hữu ích trong nhiều trường hợp. Một ví dụ giống như ở trên, nơi bạn muốn tạm dừng công việc bạn đang làm và tiếp tục công việc sau này khi có điều gì đó xảy ra (như nhận yêu cầu web hoặc thứ gì đó).

Tôi đang sử dụng chúng trong một vài dự án mà tôi đang làm việc. Trong một, tôi đang sử dụng chúng để tôi có thể đình chỉ chương trình trong khi tôi đang chờ IO qua mạng, sau đó tiếp tục lại sau. Mặt khác, tôi viết một ngôn ngữ lập trình nơi tôi cấp cho người dùng quyền truy cập vào các giá trị liên tục để họ có thể viết returnthrow cho chính họ - hoặc bất kỳ luồng kiểm soát nào khác, chẳng hạn như while vòng - mà không cần tôi làm chúng.

1

Nghĩ về chủ đề. Một luồng có thể được chạy và bạn có thể nhận được kết quả tính toán của nó. Tiếp tục là một chuỗi mà bạn có thể sao chép, vì vậy bạn có thể chạy cùng một phép tính hai lần.

1

Các vị trí tiếp tục đã được quan tâm mới với lập trình web vì chúng phản ánh tốt đặc tính tạm dừng/tiếp tục của yêu cầu web. Một máy chủ có thể xây dựng một continaution đại diện cho một phiên người dùng và tiếp tục nếu và khi người dùng tiếp tục phiên.

Các vấn đề liên quan