Tôi nên cấu trúc các lớp của mình như thế nào để cho phép mô phỏng đa luồng?


8

Trong trò chơi của tôi, có những mảnh đất có các tòa nhà (nhà ở, trung tâm tài nguyên). Các tòa nhà như nhà ở có người thuê, phòng, tiện ích bổ sung, et cetera và có một số giá trị phải được mô phỏng dựa trên tất cả các biến này.

Bây giờ, tôi muốn sử dụng AndEngine cho công cụ giao diện người dùng và tạo một luồng khác để thực hiện các tính toán mô phỏng (có lẽ sau này cũng bao gồm AI trong luồng này). Điều này là để toàn bộ một luồng không thực hiện được tất cả các công việc và gây ra các vấn đề như chặn. Điều này giới thiệu vấn đề đồng thờiphụ thuộc .

Các vấn đề tiền tệ là chính thread UI của tôi và thread tính toán sẽ cả hai cần phải truy cập vào tất cả các đối tượng mô phỏng. Vì vậy, tôi phải làm cho chúng an toàn theo luồng, nhưng tôi không biết cách lưu trữ và cấu trúc các đối tượng mô phỏng để cho phép điều đó.

Các vấn đề phụ thuộc là để các giá trị tính toán, tính toán của tôi phụ thuộc vào giá trị các đối tượng khác.

Điều gì sẽ là cách tốt nhất để liên kết đối tượng người thuê của tôi trong tòa nhà với các tính toán của tôi? Mã cứng nó vào lớp người thuê nhà? Một cách tốt để làm các thuật toán "lưu trữ" để chúng dễ dàng được điều chỉnh là gì?

Một cách lười biếng đơn giản sẽ là bung mọi thứ vào một lớp chứa tất cả các đối tượng, chẳng hạn như các mảnh đất (người lần lượt nắm giữ các tòa nhà, et cetera). Lớp này cũng sẽ giữ trạng thái trò chơi như công nghệ có sẵn cho người dùng, nhóm đối tượng cho những thứ như sprite. Nhưng đây là một cách lười biếng và nguy hiểm, đúng không?

Chỉnh sửa: Tôi đã xem xét Dependency Injection, nhưng nó đối phó như thế nào với một lớp chứa các đối tượng khác? tức là, mảnh đất của tôi, với một tòa nhà, có một người thuê và một loạt các giá trị khác. DI trông giống như một kẻ đau khổ với AndEngine.


Chỉ cần một lưu ý nhanh, không có lo ngại về việc truy cập dữ liệu đồng thời nếu một trong những truy cập đó chỉ được đọc. Miễn là bạn giữ kết xuất của mình chỉ đọc dữ liệu thô để sử dụng dữ liệu đó để kết xuất và Không cập nhật dữ liệu trong quá trình xử lý, không có vấn đề gì. Một luồng cập nhật dữ liệu, luồng khác chỉ đọc và kết xuất lại.
James

Việc truy cập đồng thời vẫn còn là một vấn đề, vì người dùng có thể mua một mảnh đất, xây dựng một tòa nhà trên mảnh đất đó và đặt một người thuê vào một ngôi nhà, do đó, chủ đề chính là tạo dữ liệu và có thể sửa đổi dữ liệu. Truy cập đồng thời không phải là vấn đề quá lớn, nó nói thêm về trường hợp chia sẻ giữa luồng chính và luồng con.
NiffyShibby

Tôi nói về sự phụ thuộc như một vấn đề có vẻ như những người như Google guice nghĩ rằng việc che giấu sự phụ thuộc không phải là một điều khôn ngoan. Tính toán của người thuê nhà của tôi phụ thuộc vào cốt truyện tòa nhà, tòa nhà, tạo ra một sprite trên màn hình (tôi có thể có mối quan hệ quan hệ giữa người thuê tòa nhà và tạo ra một người thuê nhà ở nơi khác)
NiffyShibby

Tôi đoán đề xuất của tôi nên được hiểu là làm cho những thứ bị xâu chuỗi trở thành những thứ độc lập hoặc yêu cầu quyền truy cập chỉ đọc vào dữ liệu được quản lý bởi một luồng khác .. Kết xuất sẽ là một ví dụ về thứ gì đó bạn có thể xâu chuỗi như nó sẽ chỉ cần truy cập đọc vào dữ liệu để nó có thể hiển thị chúng.
James

1
James, ngay cả một truy cập chỉ đọc có thể là một ý tưởng tồi nếu một chủ đề khác đang ở giữa thực hiện các thay đổi cho đối tượng đó. Với cấu trúc dữ liệu phức tạp, nó có thể gây ra sự cố và với các loại dữ liệu đơn giản, nó có thể gây ra lỗi đọc không nhất quán.
Kylotan

Câu trả lời:


4

Vấn đề của bạn vốn là nối tiếp - bạn phải hoàn thành cập nhật mô phỏng trước khi có thể kết xuất nó. Giảm tải mô phỏng cho một luồng khác chỉ đơn giản là luồng UI chính không làm trong khi luồng mô phỏng đánh dấu (có nghĩa là nó bị chặn).

"Thực tiễn tốt nhất" thường được tổ chức cho đồng thời là không đặt kết xuất của bạn trên một luồng và mô phỏng của bạn trên một luồng khác, như bạn đang đề xuất. Tôi thực sự khuyên bạn nên chống lại cách tiếp cận đó, trên thực tế. Hai hoạt động có liên quan đến huyết thanh một cách tự nhiên và trong khi chúng có thể bị ép buộc, nó không tối ưu và không có quy mô .

Một cách tiếp cận tốt hơn là làm cho các phần của bản cập nhật hoặc kết xuất đồng thời, nhưng để bản cập nhật và hiển thị luôn luôn nối tiếp. Vì vậy, ví dụ, nếu bạn có một ranh giới tự nhiên trong mô phỏng của mình (ví dụ: nếu các ngôi nhà không bao giờ ảnh hưởng lẫn nhau trong mô phỏng của bạn), bạn có thể chuyển tất cả các ngôi nhà vào các thùng của nhà N và quay một loạt các luồng mà mỗi quy trình xử lý xô và để các chủ đề đó tham gia trước khi bước cập nhật hoàn tất. Thang đo này tốt hơn nhiều và phù hợp hơn nhiều cho thiết kế đồng thời.

Bạn đang suy nghĩ quá mức phần còn lại của vấn đề:

Tiêm phụ thuộc là một cá trích đỏ ở đây: tất cả tiêm phụ thuộc thực sự có nghĩa là bạn vượt qua ("tiêm") các phụ thuộc của một giao diện cho các phiên bản của giao diện đó, thường là trong quá trình xây dựng.

Điều đó có nghĩa là nếu bạn có một lớp mô hình a House, cần biết những thứ về Citynó, thì hàm Housetạo có thể trông như sau:

public House( City containingCity ) {
  m_city = containingCity; // Store in a member variable for later access
  ...
}

Không có gì đặc biệt.

Sử dụng một singleton là không cần thiết (bạn thường thấy nó được thực hiện trong một số "khung DI" phức tạp, quá phức tạp như Caliburn được thiết kế cho các ứng dụng GUI "doanh nghiệp" - điều này không làm cho nó trở thành một giải pháp tốt). Trong thực tế, giới thiệu singletons thường là phản đề của quản lý phụ thuộc tốt. Chúng cũng có thể gây ra vấn đề nghiêm trọng với mã đa luồng vì chúng thường không thể được tạo luồng an toàn mà không có khóa - bạn càng phải có nhiều khóa, vấn đề của bạn càng phù hợp để xử lý theo cách song song.


Tôi nhớ rằng những người độc thân rất tệ trong bài viết gốc của tôi ...
NiffyShibby

Tôi nhớ rằng singletons là xấu trong bài viết gốc của tôi, nhưng điều đó đã bị xóa. Tôi nghĩ rằng tôi đang nhận được những gì bạn đang nói. Ví dụ, người nhỏ bé của tôi đang đi ngang qua màn hình, vì anh ta đang thực hiện rằng luồng cập nhật được gọi, nó cần cập nhật cho anh ta, nhưng không thể vì luồng chính đang sử dụng đối tượng, do đó luồng khác của tôi bị chặn. Trường hợp như tôi nên cập nhật giữa kết xuất.
NiffyS cấm

Ai đó đã gửi cho tôi một liên kết hữu ích. gamedev.stackexchange.com/questions/95/
Ấn

5

Giải pháp thông thường cho các vấn đề tương tranh là cách ly dữ liệu .

Cô lập có nghĩa là mỗi luồng có dữ liệu riêng và không chạm vào dữ liệu của các luồng khác. Bằng cách này, không có vấn đề với đồng thời ... nhưng sau đó chúng ta có vấn đề về giao tiếp. Làm thế nào các chủ đề này có thể làm việc cùng nhau nếu họ không chia sẻ bất kỳ dữ liệu?

Có hai cách tiếp cận ở đây.

Đầu tiên là bất biến . Các cấu trúc / biến không thay đổi là những cấu trúc không bao giờ thay đổi trạng thái của chúng. Lúc đầu, điều này nghe có vẻ vô dụng - làm thế nào người ta có thể sử dụng một "biến" không bao giờ thay đổi? Tuy nhiên, chúng ta có thể trao đổi các biến này! Xem xét ví dụ này: giả sử bạn có một Tenantlớp với một loạt các trường, được yêu cầu phải ở một số trạng thái nhất quán. Nếu bạn thay đổi một Tenantđối tượng trong luồng A, đồng thời quan sát nó từ luồng B, luồng B có thể thấy đối tượng ở trạng thái không nhất quán. Tuy nhiên, nếu Tenantkhông thay đổi, luồng A không thể thay đổi nó. Thay vào đó, nó tạo ra mới Tenantđối tượng với các trường được thiết lập theo yêu cầu và hoán đổi nó với trường cũ. Trao đổi chỉ là một thay đổi đối với một tham chiếu, có lẽ là nguyên tử, và do đó không có cách nào để quan sát đối tượng ở trạng thái không nhất quán.

Cách tiếp cận thứ hai là nhắn tin . Ý tưởng đằng sau đó là khi tất cả dữ liệu được "sở hữu" bởi một số luồng, chúng ta có thể cho chủ đề này biết phải làm gì với dữ liệu. Mỗi luồng trong kiến ​​trúc này có một hàng đợi tin nhắn - một danh sách các Messageđối tượng và một máy bơm nhắn tin - phương thức chạy liên tục để loại bỏ một tin nhắn khỏi hàng đợi, giải thích nó và gọi một số phương thức xử lý. Ví dụ: giả sử bạn gõ trên một mảnh đất, báo hiệu rằng nó cần phải được mua. Chuỗi UI không thể thay đổi Plotđối tượng trực tiếp, vì nó thuộc về luồng logic (và có lẽ là không thay đổi). Vì vậy, luồng UI xây dựng một BuyMessageđối tượng thay vào đó và thêm nó vào hàng đợi của luồng logic. Chuỗi logic, khi chạy, nhận thông báo từ hàng đợi và các cuộc gọiBuyPlot(), trích xuất các tham số từ đối tượng tin nhắn. Nó có thể gửi tin nhắn trở lại, ví dụ BuySuccessfulMessage, hướng dẫn luồng UI để đưa ra "Bây giờ bạn có nhiều đất hơn!" cửa sổ trên màn hình. Tất nhiên, quyền truy cập vào hàng đợi tin nhắn phải được đồng bộ hóa với khóa, phần quan trọng hoặc bất cứ thứ gì nó được gọi trong AndEngine. Nhưng đây là một điểm đồng bộ hóa duy nhất giữa các luồng và các luồng bị đình chỉ trong một thời gian rất ngắn, vì vậy nó không phải là vấn đề.

Hai phương pháp này được sử dụng tốt nhất trong sự kết hợp. Các chủ đề của bạn nên giao tiếp với các thông điệp và có một số dữ liệu bất biến "mở" cho các chủ đề khác - ví dụ: một danh sách các lô bất biến cho UI để vẽ chúng.

Cũng lưu ý rằng "chỉ đọc" không nhất thiết có nghĩa là bất biến ! Bất kỳ cấu trúc dữ liệu phức tạp nào như hashtable đều có thể thay đổi trạng thái bên trong của nó khi truy cập đọc, vì vậy hãy kiểm tra tài liệu trước.


Nghe có vẻ gọn gàng, tôi sẽ phải thực hiện một số thử nghiệm với nó, nghe có vẻ khá tốn kém theo cách này, tôi đã suy nghĩ dọc theo dòng DI với một phạm vi đơn lẻ, sau đó sử dụng khóa để truy cập đồng thời. Nhưng tôi chưa bao giờ nghĩ làm theo cách này, nó có thể hoạt động: D
NiffyShibby

Chà, đó là cách chúng tôi thực hiện đồng thời trên máy chủ đa luồng rất đồng thời. Có lẽ hơi quá mức cho một trò chơi đơn giản, nhưng đó là cách tiếp cận tôi sử dụng bản thân mình.
Nevermind

4

Có lẽ 99% các chương trình máy tính được viết trong lịch sử chỉ sử dụng 1 luồng và hoạt động tốt. Tôi không có bất kỳ kinh nghiệm nào về AndEngine nhưng rất hiếm khi tìm thấy các hệ thống yêu cầu phân luồng, chỉ một số có thể được hưởng lợi từ nó, được cung cấp phần cứng phù hợp.

Theo truyền thống, để thực hiện mô phỏng và GUI / kết xuất trong một luồng, bạn chỉ cần thực hiện một chút mô phỏng, sau đó bạn kết xuất và bạn lặp lại, thường là nhiều lần trong một giây.

Khi ai đó có ít kinh nghiệm sử dụng nhiều quy trình hoặc không đánh giá đầy đủ ý nghĩa của "an toàn" của chủ đề (đó là một thuật ngữ mơ hồ có thể có nghĩa là nhiều thứ khác nhau), quá dễ dàng để đưa nhiều lỗi vào hệ thống. Vì vậy, cá nhân tôi khuyên bạn nên thực hiện phương pháp đơn luồng, mô phỏng và kết xuất xen kẽ, và lưu bất kỳ luồng nào cho các hoạt động mà bạn biết chắc chắn sẽ mất nhiều thời gian và hoàn toàn yêu cầu các luồng chứ không phải mô hình dựa trên sự kiện.


Andengine thực hiện kết xuất cho tôi, nhưng tôi vẫn cảm thấy rằng các phép tính phải đi theo một luồng khác, vì luồng ui chính sẽ chậm lại nếu không bị chặn nếu mọi thứ được thực hiện trong một luồng.
NiffyShibby

Tại sao bạn cảm thấy điều đó? Bạn có tính toán đắt hơn một trò chơi 3D thông thường không? Và bạn có biết rằng hầu hết các thiết bị Android chỉ có 1 lõi và do đó không đạt được lợi ích hiệu năng nội tại từ các luồng bổ sung?
Kylotan

Không, nhưng thật tuyệt khi phân tách logic và xác định rõ ràng những gì đang được thực hiện, nếu bạn giữ nó trong cùng một luồng, bạn phải tham chiếu lớp chính nơi tổ chức này hoặc thực hiện một số DI với phạm vi đơn. Đó không phải là một vấn đề quá lớn. Về phần lõi, chúng ta sẽ thấy nhiều thiết bị Android lõi kép xuất hiện hơn, ý tưởng trò chơi của tôi có thể không hoạt động tốt trên một thiết bị lõi đơn trong khi trên lõi kép thì nó có thể hoạt động khá tốt.
NiffyS cấm

Vì vậy, thiết kế mọi thứ trong 1 toàn bộ luồng dường như không phải là một ý tưởng tuyệt vời đối với tôi, ít nhất là với các luồng tôi có thể tách rời logic và trong tương lai không phải lo lắng về việc cố gắng cải thiện hiệu suất như tôi đã thiết kế từ đầu.
NiffyS cấm

Nhưng vấn đề của bạn vẫn là nối tiếp, do đó, bạn có thể sẽ chặn cả hai luồng của bạn chờ chúng tham gia bằng mọi cách, trừ khi bạn đã cách ly dữ liệu (cung cấp cho luồng kết xuất một cái gì đó thực sự làm trong khi luồng logic đánh dấu) và hiển thị khung hoặc như vậy đằng sau mô phỏng. Cách tiếp cận bạn đang mô tả không phải là cách thực hành tốt nhất thường được chấp nhận cho thiết kế đồng thời.
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.