Sự khác biệt giữa một coroutine và một chủ đề là gì? Có bất kỳ lợi thế của việc sử dụng cái này hơn cái kia không?
Sự khác biệt giữa một coroutine và một chủ đề là gì? Có bất kỳ lợi thế của việc sử dụng cái này hơn cái kia không?
Câu trả lời:
Mặc dù Coroutines dường như hoạt động giống như các luồng trong cái nhìn đầu tiên, nhưng thực tế chúng không sử dụng bất kỳ đa luồng nào. Chúng được thực hiện tuần tự cho đến khi chúng yield
. Động cơ sẽ kiểm tra tất cả các coroutines mang lại như là một phần của vòng lặp chính của nó (tại điểm nào phụ thuộc chính xác vào loại yield
, kiểm tra sơ đồ này để biết thêm thông tin ), tiếp tục lần lượt cho đến khi tiếp theo yield
, và sau đó tiến hành vòng lặp chính.
Kỹ thuật này có ưu điểm là bạn có thể sử dụng coroutines mà không bị đau đầu do các vấn đề với đa luồng thực sự. Bạn sẽ không gặp phải bất kỳ bế tắc, điều kiện cuộc đua hoặc vấn đề hiệu suất nào do chuyển đổi ngữ cảnh gây ra, bạn sẽ có thể gỡ lỗi đúng cách và bạn không cần phải sử dụng các thùng chứa dữ liệu an toàn luồng. Điều này là do khi một coroutine đang được thực thi, động cơ Unity ở trạng thái được kiểm soát. Nó là an toàn để sử dụng hầu hết các chức năng Unity.
Mặt khác, với các luồng, bạn hoàn toàn không có kiến thức gì về trạng thái của vòng lặp chính Unity hiện tại (thực tế nó có thể không còn chạy nữa). Vì vậy, chủ đề của bạn có thể gây ra khá nhiều sự tàn phá bằng cách làm một cái gì đó tại một thời điểm không cần phải làm điều đó. Không chạm vào bất kỳ chức năng Unity gốc nào từ chuỗi con . Nếu bạn cần liên lạc giữa một luồng con và luồng chính của mình, hãy để luồng đó ghi vào một đối tượng container (!) An toàn luồng và có MonoBehaviour đọc thông tin đó trong các hàm sự kiện Unity thông thường.
Nhược điểm của việc không thực hiện đa luồng "thực" là bạn không thể sử dụng coroutines để song song hóa các phép tính cường độ CPU trên nhiều lõi CPU. Tuy nhiên, bạn có thể sử dụng chúng để phân chia một phép tính trên nhiều bản cập nhật. Vì vậy, thay vì đóng băng trò chơi của bạn trong một giây, bạn chỉ nhận được tốc độ khung hình trung bình thấp hơn trong nhiều giây. Nhưng trong trường hợp đó, bạn phải chịu trách nhiệm với yield
coroutine của mình bất cứ khi nào bạn muốn cho phép Unity chạy bản cập nhật.
Phần kết luận:
Coroutines là những gì chúng ta trong Khoa học Máy tính gọi là "đa nhiệm hợp tác". Chúng là một cách để nhiều luồng thực thi khác nhau xen kẽ với nhau. Trong đa nhiệm hợp tác, một luồng thực thi có quyền sở hữu duy nhất đối với CPU cho đến khi đạt được a yield
. Tại thời điểm đó, Unity (hoặc bất kỳ khuôn khổ nào bạn đang sử dụng), có tùy chọn để chuyển sang một luồng thực thi khác. Sau đó, nó cũng có quyền sở hữu duy nhất đối với CPU cho đến khiyield
s.
Chủ đề là những gì chúng ta gọi là "đa nhiệm ưu tiên." Khi bạn đang sử dụng các chủ đề, khung bảo lưu quyền tại bất cứ lúc nào để dừng luồng giữa luồng của bạn và chuyển sang luồng khác. Không quan trọng bạn đang ở đâu. Bạn thậm chí có thể bị dừng một phần thông qua việc viết một biến vào bộ nhớ trong một số trường hợp!
Có những ưu và nhược điểm cho mỗi. Nhược điểm của coroutines có lẽ là dễ hiểu nhất. Trước hết, tất cả các coroutines thực hiện trên một lõi đơn. Nếu bạn có CPU lõi tứ, coroutines sẽ chỉ sử dụng một trong bốn lõi. Điều này đơn giản hóa mọi thứ, nhưng có thể là một vấn đề hiệu suất trong một số trường hợp. Nhược điểm thứ hai là bạn phải nhận thức được rằng bất kỳ coroutine nào cũng có thể dừng toàn bộ chương trình của bạn chỉ bằng cách từ chối yield
. Đây là một vấn đề trên Mac OS9, nhiều năm trước. OS9 chỉ hỗ trợ đa nhiệm hợp tác, trên toàn bộ máy tính. Nếu một trong các chương trình của bạn bị treo, nó có thể khiến máy tính dừng lại dữ dội đến mức HĐH thậm chí không thể hiển thị văn bản của thông báo lỗi để cho bạn biết điều gì đã xảy ra!
Ưu điểm của coroutines là chúng tương đối dễ hiểu. Các lỗi bạn có rất nhiều dự đoán. Họ cũng thường kêu gọi ít tài nguyên hơn, điều này có thể hữu ích khi bạn leo lên 10 ngàn trong số hàng ngàn xác chết hoặc chủ đề. Candid Moon đã đề cập trong các bình luận rằng, nếu bạn chưa nghiên cứu các chủ đề một cách chính xác, chỉ cần bám vào các coroutines, và họ đã đúng. Coroutines đơn giản hơn nhiều để làm việc với.
Chủ đề là một con thú khác nhau hoàn toàn. Bạn luôn phải cảnh giác trước khả năng một luồng khác có thể làm gián đoạn bạn bất cứ lúc nàovà gây rối với dữ liệu của bạn. Các thư viện luồng cung cấp toàn bộ bộ công cụ mạnh mẽ để hỗ trợ bạn điều này, chẳng hạn như mutexes và các biến điều kiện giúp bạn nói với HĐH khi nó an toàn để chạy một trong các luồng khác của bạn và khi nó không an toàn. Có toàn bộ các khóa học dành riêng cho cách sử dụng các công cụ này tốt. Một trong những vấn đề nổi tiếng nảy sinh là "bế tắc", đó là khi hai luồng cả hai bị "kẹt" chờ người kia giải phóng một số tài nguyên. Một vấn đề khác, rất quan trọng đối với Unity, đó là nhiều thư viện (như Unity) không được thiết kế để hỗ trợ các cuộc gọi từ nhiều luồng. Bạn có thể rất dễ dàng phá vỡ khuôn khổ của mình nếu bạn không chú ý đến những cuộc gọi nào được phép và những cuộc gọi nào bị cấm.
Lý do cho sự phức tạp thêm này thực sự rất đơn giản. Mô hình đa nhiệm được ưu tiên thực sự giống với mô hình đa luồng cho phép bạn không chỉ làm gián đoạn các luồng khác mà còn thực sự chạy các luồng song song trên các lõi khác nhau. Điều này là vô cùng mạnh mẽ, là cách duy nhất để thực sự tận dụng các CPU lõi tứ và mã hex mới sắp ra mắt, nhưng mở ra hộp pandoras. Các quy tắc đồng bộ hóa về cách quản lý dữ liệu này trong một thế giới đa luồng rất tích cực. Trong thế giới C ++, có toàn bộ các bài viết dành riêng cho MEMORY_ORDER_CONSUME
một góc itty-bitty-teeny-weenie của đồng bộ hóa đa luồng.
Vậy nhược điểm của luồng? Đơn giản: chúng khó. Bạn có thể bắt gặp toàn bộ các lớp lỗi mà bạn chưa từng thấy trước đây. Nhiều người được gọi là "heisenbugs" đôi khi xuất hiện và sau đó biến mất khi bạn gỡ lỗi chúng. Các công cụ bạn được cung cấp để đối phó với những công cụ này rất mạnh mẽ, nhưng chúng cũng ở mức rất thấp. Chúng được thiết kế để có hiệu quả trên các kiến trúc của chip hiện đại thay vì được thiết kế để dễ sử dụng.
Tuy nhiên, nếu bạn muốn sử dụng tất cả sức mạnh CPU của mình, chúng là công cụ bạn cần. Ngoài ra, có là thuật toán thực sự dễ hiểu hơn trong đa luồng so với các coroutines đơn giản chỉ vì bạn để HĐH xử lý tất cả các câu hỏi về nơi có thể xảy ra gián đoạn.
Nhận xét của Candid Moon để bám vào coroutines là đề xuất của tôi. Nếu bạn muốn sức mạnh của chủ đề, sau đó cam kết với nó. Đi ra ngoài và thực sự tìm hiểu chủ đề, chính thức. Chúng tôi đã có vài thập kỷ để tìm ra cách tổ chức cách tốt nhất để suy nghĩ về các chủ đề để bạn sớm nhận được kết quả lặp lại đáng tin cậy an toàn và thêm hiệu suất khi bạn đi. Ví dụ, tất cả các khóa học lành mạnh sẽ dạy mutexes trước khi dạy các biến điều kiện. Tất cả các khóa học lành mạnh bao gồm các nguyên tử sẽ dạy đầy đủ các biến và điều kiện biến trước khi thậm chí đề cập rằng nguyên tử tồn tại. (Lưu ý: không có thứ gọi là hướng dẫn lành mạnh về nguyên tử.) Hãy thử học từng phần, và bạn đang cầu xin chứng đau nửa đầu.
join()
, nhưng bạn cần một cái gì đó. Nếu bạn không có một kiến trúc sư thiết kế một hệ thống để thực hiện đồng bộ hóa cho bạn, bạn phải tự viết nó. Là một người làm công việc đa luồng, tôi thấy rằng các mô hình tinh thần của mọi người về cách máy tính hoạt động cần điều chỉnh trước khi họ đồng bộ hóa một cách an toàn (đó là những gì các khóa học tốt sẽ dạy)
join
. Quan điểm của tôi là việc theo đuổi một phần vừa phải các lợi ích hiệu suất có thể có của luồng, đôi khi, có thể tốt hơn so với sử dụng phương pháp phân luồng phức tạp hơn để gặt hái một phần lớn hơn.
Nói một cách đơn giản nhất có thể ...
Chủ đề
Một luồng không quyết định khi nào nó sinh ra, hệ điều hành ('HĐH', ví dụ Windows) quyết định khi một luồng được tạo ra. Hệ điều hành gần như hoàn toàn chịu trách nhiệm lên lịch cho các luồng, nó quyết định các luồng nào sẽ chạy, khi nào chạy chúng và trong bao lâu.
Ngoài ra, một luồng có thể được chạy đồng bộ (hết luồng này đến luồng khác) hoặc không đồng bộ (các luồng khác nhau chạy trên các lõi CPU khác nhau). Khả năng chạy không đồng bộ có nghĩa là các luồng có thể hoàn thành nhiều công việc hơn trong cùng một khoảng thời gian (vì các luồng theo nghĩa đen đang làm hai việc cùng một lúc). Ngay cả các luồng đồng bộ cũng có rất nhiều công việc được thực hiện nếu HĐH tốt trong việc lên lịch cho chúng.
Tuy nhiên, sức mạnh xử lý thêm này đi kèm với tác dụng phụ. Ví dụ: nếu hai luồng đang cố truy cập vào cùng một tài nguyên (ví dụ: một danh sách) và mỗi luồng có thể được dừng ngẫu nhiên tại bất kỳ điểm nào trong mã, các sửa đổi của luồng thứ hai có thể can thiệp vào các sửa đổi được tạo bởi luồng đầu tiên. (Xem thêm: Điều kiện cuộc đua và bế tắc .)
Chủ đề cũng được coi là "nặng" vì chúng có rất nhiều chi phí có nghĩa là có một hình phạt thời gian đáng kể phát sinh khi chuyển đổi chủ đề.
Quân đoàn
Không giống như các luồng, coroutines hoàn toàn đồng bộ, chỉ có một coroutine có thể chạy bất cứ lúc nào. Ngoài ra, các coroutine có thể chọn thời điểm mang lại, và do đó có thể chọn mang lại một điểm trong mã phù hợp (ví dụ: ở cuối chu kỳ vòng lặp). Điều này có lợi thế của các vấn đề như điều kiện chủng tộc và bế tắc dễ tránh hơn nhiều, cũng như giúp các coroutine dễ dàng hợp tác với nhau hơn.
Tuy nhiên đây cũng là một trách nhiệm lớn, nếu một coroutine không mang lại kết quả đúng, nó có thể sẽ tiêu tốn rất nhiều thời gian của bộ xử lý và nó vẫn có thể gây ra lỗi nếu sửa đổi tài nguyên được chia sẻ không chính xác.
Coroutines thường không yêu cầu chuyển đổi ngữ cảnh và do đó nhanh chóng chuyển đổi vào và ra và khá nhẹ.
Tóm tắt:
Chủ đề:
Quân đoàn
Vai trò của chủ đề và coroutines rất giống nhau, nhưng chúng khác nhau về cách chúng hoàn thành công việc, điều đó có nghĩa là mỗi công việc phù hợp hơn với các nhiệm vụ khác nhau. Chủ đề là tốt nhất cho các nhiệm vụ mà họ có thể tập trung vào việc tự làm một cái gì đó mà không bị gián đoạn, sau đó báo hiệu trở lại khi hoàn thành. Coroutines là tốt nhất cho các nhiệm vụ có thể được thực hiện trong nhiều bước nhỏ và các nhiệm vụ yêu cầu xử lý dữ liệu hợp tác.