Là một cấu trúc ngăn xếp được sử dụng cho các quá trình không đồng bộ?


10

Câu hỏi này có một câu trả lời tuyệt vời của Eric Lippert mô tả những gì ngăn xếp được sử dụng cho. Trong một năm tôi đã biết - nói chung - stack là gì và được sử dụng như thế nào, nhưng các phần trong câu trả lời của anh ấy khiến tôi tự hỏi liệu cấu trúc ngăn xếp này ngày nay ít được sử dụng trong đó lập trình async là chuẩn mực.

Từ câu trả lời của anh ấy:

ngăn xếp là một phần của sự hợp nhất hóa tiếp tục trong một ngôn ngữ không có coroutines.

Cụ thể, phần không có coroutines của điều này có tôi tự hỏi.

Ông giải thích thêm một chút ở đây:

Coroutines là các chức năng có thể nhớ vị trí của chúng, kiểm soát năng suất cho một coroutine khác trong một thời gian, và sau đó tiếp tục nơi chúng rời đi sau đó, nhưng không nhất thiết phải ngay sau khi sản lượng coroutine được gọi là. Hãy nghĩ đến "lợi nhuận mang lại" hoặc "chờ đợi" trong C #, phải nhớ vị trí của chúng khi mục tiếp theo được yêu cầu hoặc hoạt động không đồng bộ hoàn tất. Các ngôn ngữ có coroutines hoặc các tính năng ngôn ngữ tương tự đòi hỏi các cấu trúc dữ liệu nâng cao hơn so với ngăn xếp để thực hiện tiếp tục.

Điều này là tuyệt vời đối với ngăn xếp, nhưng lại cho tôi một câu hỏi chưa được trả lời về cấu trúc nào được sử dụng khi ngăn xếp quá đơn giản để xử lý các tính năng ngôn ngữ này đòi hỏi cấu trúc dữ liệu nâng cao hơn?

Là ngăn xếp đi xa khi công nghệ tiến bộ? Cái gì thay thế nó? Nó có phải là một loại lai của sự vật? (ví dụ, chương trình .NET của tôi có sử dụng ngăn xếp cho đến khi nó thực hiện cuộc gọi không đồng bộ sau đó chuyển sang một cấu trúc khác cho đến khi hoàn thành, tại thời điểm đó, ngăn xếp không quay trở lại trạng thái có thể chắc chắn về các mục tiếp theo, v.v.? )

Rằng các kịch bản này quá tiên tiến cho một ngăn xếp có ý nghĩa hoàn hảo, nhưng điều gì thay thế cho ngăn xếp? Khi tôi đã biết về những năm trước đây, ngăn xếp ở đó vì nó nhanh và nhẹ, một phần bộ nhớ được phân bổ tại ứng dụng cách xa heap vì nó hỗ trợ quản lý hiệu quả cao cho nhiệm vụ trong tay (ý định chơi chữ?). Có gì thay đổi?

Câu trả lời:


14

Nó có phải là một loại lai của sự vật? (vd )

Về cơ bản là có.

Giả sử chúng ta có

async void MyButton_OnClick() { await Foo(); Bar(); }
async Task Foo() { await Task.Delay(123); Blah(); }

Đây là một lời giải thích cực kỳ đơn giản về cách tiếp tục được thống nhất. Mã thực sự phức tạp hơn đáng kể, nhưng điều này có ý tưởng xuyên suốt.

Bạn bấm vào nút. Một tin nhắn được xếp hàng. Vòng lặp thông báo xử lý tin nhắn và gọi trình xử lý nhấp chuột, đặt địa chỉ trả về của hàng đợi tin nhắn trên ngăn xếp. Đó là, điều xảy ra sau khi xử lý xong là vòng lặp thông báo phải tiếp tục chạy. Vì vậy, sự tiếp tục của xử lý là vòng lặp.

Trình xử lý nhấp chuột gọi Foo (), đặt địa chỉ trả về của chính nó trên ngăn xếp. Đó là, phần tiếp theo của Foo là phần còn lại của trình xử lý nhấp chuột.

Foo gọi Task.Delay, đặt địa chỉ trả về của chính nó trên ngăn xếp.

Task.Delay thực hiện bất kỳ phép thuật nào cần làm để trả lại ngay một Nhiệm vụ. Ngăn xếp được bật lên và chúng tôi trở lại Foo.

Foo kiểm tra tác vụ được trả về để xem nó đã hoàn thành chưa. Không phải vậy. Các sự tiếp nối của chờ đợi là để gọi Blah (), vì vậy Foo tạo ra một đại biểu trong đó kêu gọi Blah (), và dấu hiệu cho thấy ủy lên như việc tiếp tục nhiệm vụ. (Tôi chỉ đưa ra một tuyên bố sai lầm nhỏ; bạn có nắm bắt được không? Nếu không, chúng tôi sẽ tiết lộ ngay lập tức.)

Foo sau đó tạo đối tượng Nhiệm vụ riêng của mình, đánh dấu nó là chưa hoàn thành và trả nó lên ngăn xếp cho trình xử lý nhấp chuột.

Trình xử lý nhấp chuột kiểm tra nhiệm vụ của Foo và phát hiện ra nó chưa hoàn thành. Việc tiếp tục chờ đợi trong trình xử lý là gọi Bar (), vì vậy trình xử lý nhấp chuột tạo ra một ủy nhiệm gọi Bar () và đặt nó làm tiếp tục nhiệm vụ được Foo () trả về. Sau đó, nó trả lại ngăn xếp vào vòng lặp tin nhắn.

Vòng lặp tin nhắn tiếp tục xử lý tin nhắn. Cuối cùng, ma thuật hẹn giờ được tạo bởi tác vụ trì hoãn thực hiện công việc của nó và gửi một thông báo tới hàng đợi nói rằng việc tiếp tục tác vụ trì hoãn có thể được thực thi. Vì vậy, vòng lặp thông báo gọi tiếp tục nhiệm vụ, đặt chính nó vào ngăn xếp như bình thường. Đại biểu đó gọi Blah (). Blah () làm những gì nó làm và trả lại stack.

Bây giờ chuyện gì xảy ra? Đây là một chút khó khăn. Việc tiếp tục nhiệm vụ trì hoãn không chỉ gọi Blah (). Nó cũng phải kích hoạt một cuộc gọi đến Bar () , nhưng nhiệm vụ đó không biết về Bar!

Foo thực sự đã tạo ra một đại biểu mà (1) gọi Blah () và (2) gọi việc tiếp tục nhiệm vụ mà Foo đã tạo và giao lại cho người xử lý sự kiện. Đó là cách chúng tôi gọi một đại biểu gọi Bar ().

Và bây giờ chúng tôi đã làm mọi thứ chúng tôi cần làm, theo đúng thứ tự. Nhưng chúng tôi không bao giờ ngừng xử lý tin nhắn trong vòng lặp tin nhắn trong một thời gian dài, vì vậy ứng dụng vẫn phản hồi.

Rằng các kịch bản này quá tiên tiến cho một ngăn xếp có ý nghĩa hoàn hảo, nhưng điều gì thay thế cho ngăn xếp?

Một biểu đồ của các đối tượng tác vụ chứa các tham chiếu cho nhau thông qua các lớp đóng của các đại biểu. Các lớp đóng cửa đó là các máy trạng thái theo dõi vị trí của việc chờ đợi được thực hiện gần đây nhất và các giá trị của người dân địa phương. Thêm vào đó, trong ví dụ đã cho, một hàng đợi các hành động ở trạng thái toàn cầu được thực thi bởi hệ điều hành và vòng lặp thông báo thực thi các hành động đó.

Bài tập: làm thế nào để bạn cho rằng tất cả điều này hoạt động trong một thế giới không có vòng lặp thông điệp? Ví dụ, các ứng dụng giao diện điều khiển. chờ đợi trong một ứng dụng giao diện điều khiển khá khác nhau; bạn có thể suy luận làm thế nào nó hoạt động từ những gì bạn biết cho đến nay?

Khi tôi đã biết về những năm trước đây, ngăn xếp ở đó vì nó nhanh và nhẹ, một phần bộ nhớ được phân bổ tại ứng dụng cách xa heap vì nó hỗ trợ quản lý hiệu quả cao cho nhiệm vụ trong tay (ý định chơi chữ?). Có gì thay đổi?

Ngăn xếp là một cấu trúc dữ liệu hữu ích khi vòng đời kích hoạt phương thức tạo thành một ngăn xếp, nhưng trong ví dụ của tôi, các kích hoạt của trình xử lý nhấp chuột, Foo, Bar và Blah không tạo thành một ngăn xếp. Và do đó, cấu trúc dữ liệu đại diện cho dòng công việc đó không thể là một ngăn xếp; đúng hơn nó là một biểu đồ của các nhiệm vụ được phân bổ heap và các đại biểu đại diện cho một quy trình công việc. Sự chờ đợi là những điểm trong quy trình làm việc mà tiến trình không thể được thực hiện thêm trong quy trình làm việc cho đến khi công việc bắt đầu sớm hơn đã hoàn thành; trong khi chờ đợi, chúng tôi có thể thực hiện các công việc khác không phụ thuộc vào các nhiệm vụ bắt đầu cụ thể đó đã hoàn thành.

Ngăn xếp chỉ là một mảng các khung, trong đó các khung chứa (1) con trỏ đến giữa các hàm (nơi xảy ra cuộc gọi) và (2) giá trị của các biến và temps cục bộ. Các nhiệm vụ tiếp tục giống nhau: ủy nhiệm là một con trỏ tới hàm và nó có trạng thái tham chiếu một điểm cụ thể ở giữa hàm (trong đó chờ đợi xảy ra) và bao đóng có các trường cho từng biến cục bộ hoặc tạm thời . Các khung không tạo thành một mảng gọn gàng đẹp nữa, nhưng tất cả các thông tin đều giống nhau.


1
Rất hữu ích, cảm ơn. Nếu tôi có thể đánh dấu cả hai câu trả lời là một câu trả lời tôi sẽ chấp nhận, nhưng vì tôi không thể để trống chúng (nhưng, không muốn ai nghĩ rằng thời gian trả lời không được đánh giá cao)
jleach

3
@ jdl134679: Tôi đề nghị bạn đánh dấu một cái gì đó là câu trả lời nếu bạn cảm thấy câu hỏi của mình đã được trả lời; sẽ gửi một tín hiệu rằng mọi người nên đến đây nếu họ muốn đọc một câu trả lời tốt hơn là viết một câu trả lời . (Tất nhiên viết câu trả lời tốt luôn được khuyến khích.) Tôi không quan tâm ai sẽ đánh dấu.
Eric Lippert

8

Appel đã viết một bộ sưu tập rác giấy cũ có thể nhanh hơn phân bổ ngăn xếp . Đọc thêm Biên dịch của anh ấy với cuốn sách tiếp tụccẩm nang thu gom rác . Một số kỹ thuật GC là (vô tình) rất hiệu quả. Các đi qua phong cách tiếp tục định nghĩa một kinh điển toàn bộ chương trình chuyển đổi (CPS chuyển đổi ) để thoát khỏi ngăn xếp (theo lý thuyết thay thế khung cuộc gọi với đống phân bổ đóng cửa , hay nói cách khác "reifying" khung cuộc gọi cá nhân như "giá trị" cá nhân hoặc "đối tượng" ).

Nhưng ngăn xếp cuộc gọi vẫn được sử dụng rất rộng rãi và các bộ xử lý hiện tại có phần cứng chuyên dụng (thanh ghi ngăn xếp, máy bộ đệm, ....) dành riêng cho ngăn xếp cuộc gọi (và đó là vì hầu hết các ngôn ngữ lập trình cấp thấp, đặc biệt là C, dễ dàng hơn thực hiện với một ngăn xếp cuộc gọi). Cũng lưu ý rằng ngăn xếp là thân thiện với bộ đệm (và điều đó rất quan trọng đối với hiệu suất).

Thực tế mà nói, ngăn xếp cuộc gọi vẫn còn ở đây. Nhưng hiện tại chúng tôi có rất nhiều trong số chúng và đôi khi ngăn xếp cuộc gọi được chia thành nhiều phân đoạn nhỏ hơn (ví dụ: một vài trang 4Kbyte mỗi trang), đôi khi là rác được thu thập hoặc phân bổ đống. Các phân đoạn ngăn xếp này có thể được tổ chức trong một số danh sách được liên kết (hoặc một số cấu trúc dữ liệu phức tạp hơn, khi cần thiết). Ví dụ: trình biên dịch GCC một -fsplit-stacktùy chọn (đặc biệt hữu ích cho Go và "goroutines" và "quy trình không đồng bộ" của nó). Với các ngăn xếp tách, bạn có thể có hàng ngàn ngăn xếp (và các thói quen đồng thời trở nên dễ thực hiện hơn) được tạo thành từ hàng triệu phân đoạn ngăn xếp nhỏ và "tháo gỡ" ngăn xếp có thể nhanh hơn (hoặc ít nhất là nhanh như với một đoạn đơn cây rơm).

(nói cách khác, sự khác biệt giữa stack & heap bị mờ, nhưng có thể yêu cầu chuyển đổi toàn bộ chương trình hoặc thay đổi không thể thay đổi quy ước gọi và trình biên dịch)

Cũng xem cái này & cái kia và nhiều bài báo (ví dụ này ) thảo luận về chuyển đổi CPS. Đọc thêm về ASLRgọi / cc . Đọc (& STFW) thêm về các phần tiếp theo .

Việc triển khai .CLR & .NET có thể không có chuyển đổi GC & CPS tiên tiến, vì nhiều lý do thực dụng. Nó là một sự đánh đổi liên quan đến toàn bộ các biến đổi chương trình (và dễ dàng sử dụng các thường trình C cấp thấp & có thời gian chạy được mã hóa bằng C hoặc C ++).

Chicken Scheme đang sử dụng ngăn xếp máy (hoặc C) theo cách không theo quy tắc với chuyển đổi CPS: mọi phân bổ xảy ra trên ngăn xếp và khi nó trở nên quá lớn, bước sao chép và chuyển tiếp GC thế hệ xảy ra để di chuyển các giá trị được cấp phát của ngăn xếp gần đây (và có lẽ là tiếp tục hiện tại) đến đống, và sau đó ngăn xếp được giảm mạnh với một lượng lớn setjmp.


Đọc thêm SICP , Ngôn ngữ lập trình thực dụng , Sách rồng , Lisp trong những mảnh nhỏ .


1
Rất hữu ích, cảm ơn. Nếu tôi có thể đánh dấu cả hai câu trả lời là một câu trả lời tôi sẽ chấp nhận, nhưng vì tôi không thể để trống chúng (nhưng, không muốn ai nghĩ rằng thời gian trả lời không được đánh giá cao)
jleach
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.