Khi phát triển một phần mềm, khi nào bạn bắt đầu suy nghĩ / thiết kế các phần đồng thời?


8

Theo nguyên tắc không tối ưu hóa quá sớm, tôi tự hỏi tại thời điểm nào trong thiết kế / phát triển của một phần mềm, bạn có bắt đầu nghĩ về các cơ hội đồng thời không?

Tôi cũng có thể tưởng tượng rằng một chiến lược sẽ là viết một ứng dụng theo luồng đơn và thông qua hồ sơ xác định các phần là ứng cử viên để chạy song song. Một chiến lược khác mà tôi đã thấy một chút là xem xét phần mềm theo các nhóm nhiệm vụ và thực hiện song song các nhiệm vụ độc lập.

Một phần lý do để hỏi là tất nhiên, nếu bạn đợi cho đến khi kết thúc và chỉ tái cấu trúc phần mềm để hoạt động đồng thời, bạn có thể cấu trúc mọi thứ theo cách tồi tệ nhất có thể và có một nhiệm vụ chính trong tay bạn.

Những kinh nghiệm nào đã giúp xác định khi bạn xem xét sự song song trong thiết kế của bạn?

Câu trả lời:


7

Điều về các tác vụ luồng là bạn muốn có sự gắn kết cao, khớp nối thấp và đóng gói tốt. Thật thú vị, những điều đó cũng là mục tiêu thiết kế xứng đáng cho các ứng dụng đơn luồng. Tôi đã có một nhiệm vụ ngày hôm nay mà ban đầu tôi không có kế hoạch song song, nhưng khi tôi thực hiện, nó liên quan ít nhiều đến việc đổi tên một hàm run()và thay đổi cách gọi của nó.

Không tối ưu hóa quá sớm có nghĩa là đừng đặt mọi thứ vào một chủ đề "chỉ trong trường hợp", nhưng bạn cũng không nên vẽ mình vào một góc kiến ​​trúc nên sẽ rất khó để tối ưu hóa khi cần.


Có, chức năng đăng ký lại, hàng đợi công việc và những thứ tương tự có thể giúp trong các luồng đơn cũng như trong các ứng dụng đa luồng, nhưng chúng cũng cho phép mở rộng tốt để xử lý song song sau này. Mà tiết kiệm rất nhiều đau đầu với các vấn đề đồng bộ hóa sau này.
Coder

2

Các lập trình viên Java nên nắm lấy Callablegiao diện cho các đơn vị công việc. Nếu toàn bộ ứng dụng của bạn bao gồm một vòng lặp tạo Callables, vận chuyển tất cả các đơn vị cho Executor, xử lý bất kỳ tác vụ tạo bài nào, bạn có thứ gì đó rất dễ dàng được định hình thành xử lý nối tiếp, "ba hàng đợi công việc" và "làm tất cả cùng một lúc "đơn giản bằng cách chọn đúng người thực thi.

Chúng ta đang dần thích nghi với mô hình này vì điều rất phổ biến là cách tiếp cận nối tiếp trở nên quá chậm tại một thời điểm và sau đó chúng ta cần phải thực hiện nó.


1

Nó thay đổi theo dự án. Đôi khi rất dễ dàng để xem những gì có thể được thực hiện song song: có lẽ chương trình của bạn xử lý các lô tệp. Giả sử rằng việc xử lý mỗi tệp hoàn toàn độc lập với tất cả các tệp khác, vì vậy có thể khá rõ ràng rằng bạn có thể xử lý 1 tệp tại một thời điểm, hoặc 10 hoặc 100 và không có công việc nào trong số này sẽ ảnh hưởng đến các công việc khác.

Nó phức tạp hơn một chút khi các công việc song song tiềm năng không giống nhau. Xử lý tệp hình ảnh, bạn có thể có một công việc tạo biểu đồ, công việc khác tạo hình thu nhỏ và có thể là công việc khác trích xuất siêu dữ liệu EXIF ​​và sau đó là công việc cuối cùng lấy đầu ra của tất cả các công việc này và lưu trữ chúng trong cơ sở dữ liệu. Trong ví dụ này, có thể không rõ liệu chúng có nên được chạy song song hay không (nếu công việc cuối cùng sẽ phải chờ các công việc trước đó hoàn thành thành công).

Theo kinh nghiệm của tôi, cách dễ nhất để song song hóa một cái gì đó là tìm kiếm các quy trình có thể được chạy độc lập nhất có thể (như trong ví dụ đầu tiên) và bắt đầu với các quy trình đó. Tôi chỉ cố gắng làm cho ví dụ thứ hai chạy song song nếu tôi nghĩ tôi sẽ đạt được hiệu suất đáng kể trong hiệu suất với nó.


1

Bạn phải thiết kế đồng thời vào ứng dụng của bạn ngay từ đầu. Thông thường như là một tối ưu hóa, tôi sẽ đồng ý rằng nó sẽ được để lại cho đến sau này nếu không rõ ràng. Vấn đề là đồng thời có thể yêu cầu tái kiến ​​trúc ứng dụng của bạn từ đầu trong trường hợp xấu nhất - một số hệ thống hầu như không thể xử lý đồng thời. Một ví dụ dễ hiểu về điều này là các hệ thống chia sẻ dữ liệu - ví dụ: các khía cạnh mô phỏng và kết xuất của trò chơi.


0

Tôi muốn nói rằng luồng là một phần của kiến ​​trúc của ứng dụng. Vì vậy, đây là một trong những điều đầu tiên tôi cần nghĩ đến.

Ví dụ: khi tôi làm một ứng dụng GUI, mã GUI là một luồng nên các tác vụ chạy dài (ví dụ: xử lý XML) sẽ chặn GUI và thay vào đó nên chạy trong một luồng nền.

Ví dụ: một máy chủ sẽ dựa trên luồng, trong đó mỗi yêu cầu được xử lý bởi một luồng mới hoặc máy chủ có thể được điều khiển theo sự kiện và chỉ sử dụng một luồng cho mỗi lõi cpu, nhưng một lần nữa, các tác vụ chạy dài sẽ được chạy trong một chủ đề nền hoặc được chia thành các nhiệm vụ nhỏ hơn.


0

Với cách tôi tiếp cận mọi thứ, loại đa luồng miễn phí và tương đối đơn giản để áp dụng trong nhận thức muộn. Nhưng tôi đang nghĩ về dữ liệu đầu tiên. Tôi không biết nếu điều này hoạt động cho tất cả các tên miền nhưng tôi sẽ cố gắng bao quát cách tôi đi về nó.

Vì vậy, trước tiên, tất cả là về loại dữ liệu thô nhất cần thiết cho phần mềm sẽ được xử lý thường xuyên. Nếu đó là một trò chơi có thể là những thứ như mắt lưới, âm thanh, chuyển động, phát hạt, ánh sáng, kết cấu, những thứ thuộc loại này. Và tất nhiên, có rất nhiều điều phải suy nghĩ nếu bạn đi sâu vào chỉ lưới và suy nghĩ về cách chúng nên được thể hiện, nhưng chúng ta sẽ bỏ qua điều đó ngay bây giờ. Ngay bây giờ chúng tôi đang suy nghĩ ở cấp độ kiến ​​trúc rộng nhất.

Và suy nghĩ đầu tiên của tôi là "Làm thế nào để chúng ta thống nhất đại diện cho tất cả những điều này để chúng ta có thể đạt được một mẫu truy cập tương đối thống nhất cho tất cả các loại điều này?" Và suy nghĩ đầu tiên của tôi có thể là lưu trữ mọi loại vật thể trong mảng liền kề của riêng nó với một cách loại danh sách miễn phí để lấy lại các khoảng trống. Và điều đó có xu hướng thống nhất API để chúng ta có thể dễ dàng hơn, giả sử, sử dụng cùng một loại mã để tuần tự hóa các mắt lưới khi chúng ta chiếu sáng và kết cấu, ít nhất là ở nơi và cách các thành phần này được truy cập. Chúng ta càng có thể thống nhất cách mọi thứ được thể hiện, mã càng truy cập vào những thứ đó có xu hướng có hình dạng đồng nhất.

Thật tuyệt. Bây giờ chúng ta cũng có thể chỉ ra những điều này với các chỉ số 32 bit và chỉ chiếm một nửa bộ nhớ của con trỏ 64 bit. Và này, bây giờ chúng ta có thể thiết lập các giao điểm trong thời gian tuyến tính nếu chúng ta có thể liên kết một bitet song song, ví dụ: Chúng ta cũng có thể liên kết dữ liệu với bất kỳ một trong những điều này rất rẻ song song vì chúng ta lập chỉ mục mọi thứ. Oh và bitet đó có thể cung cấp cho chúng ta một tập hợp các chỉ mục được sắp xếp để duyệt theo thứ tự tuần tự cho các mẫu truy cập bộ nhớ được cải thiện, không phải tải lại cùng một dòng bộ đệm trong một vòng lặp. Chúng tôi có thể kiểm tra 64 bit cùng một lúc. Nếu tất cả 64 bit không được đặt, chúng ta có thể bỏ qua hơn 64 phần tử cùng một lúc. Nếu tất cả chúng được đặt, chúng ta có thể xử lý tất cả chúng cùng một lúc. Nếu một số được đặt nhưng không phải tất cả, chúng ta có thể sử dụng các lệnh FFS để nhanh chóng xác định bit nào được đặt.

Nhưng chờ đã, điều đó thật tốn kém nếu chúng ta chỉ muốn liên kết dữ liệu với vài trăm thứ trong số hàng chục ngàn thứ. Vì vậy, hãy sử dụng một mảng thưa thớt thay vào đó, như vậy:

nhập mô tả hình ảnh ở đây

Và này, bây giờ chúng ta đã có mọi thứ được lưu trữ trong các mảng thưa thớt và lập chỉ mục cho chúng, sẽ khá dễ dàng để biến điều này thành một cấu trúc dữ liệu liên tục.

nhập mô tả hình ảnh ở đây

Bây giờ chúng ta có thể viết các hàm giá rẻ hơn không có tác dụng phụ vì chúng không cần sao chép sâu những gì chưa thay đổi.

Và ở đây tôi đã được cung cấp một bảng cheat sau khi tìm hiểu về các công cụ ECS, nhưng bây giờ chúng ta hãy nghĩ về loại chức năng rộng nào sẽ hoạt động trên mỗi loại thành phần. Chúng ta có thể gọi những "hệ thống" này. "SoundSystem" có thể xử lý các thành phần "Âm thanh". Mỗi hệ thống là một chức năng rộng hoạt động trên một hoặc nhiều loại dữ liệu.

nhập mô tả hình ảnh ở đây

Điều đó khiến chúng ta có nhiều trường hợp, đối với bất kỳ loại thành phần nhất định, chỉ có một hoặc hai hệ thống sẽ truy cập chúng. Hmm, điều đó có vẻ như sẽ giúp ích cho sự an toàn của luồng và hoàn toàn đưa sự tranh chấp về mức tối thiểu.

Hơn nữa, tôi cố gắng nghĩ về cách thực hiện đồng nhất dữ liệu. Thay vì thích:

for each thing:
    play with it
    cuddle it
    kill it

Tôi tìm cách chia nó thành nhiều lượt, đơn giản hơn:

for each thing:
    play with it
for each thing:
    cuddle it
for each thing:
    kill it

Điều đó đôi khi yêu cầu lưu trữ một số trạng thái trung gian cho quá trình trì hoãn đồng nhất tiếp theo để xử lý nhưng tôi thấy điều đó thực sự giúp tôi duy trì và suy luận về mã, biết rằng mỗi vòng lặp có logic đơn giản hơn, thống nhất hơn. Và này, có vẻ như nó sẽ đơn giản hóa sự an toàn của luồng và giảm sự tranh chấp của luồng.

Và bạn cứ tiếp tục như vậy cho đến khi bạn thấy rằng bạn có một kiến ​​trúc thực sự dễ dàng song song với sự tự tin về tính an toàn và tính chính xác của luồng, nhưng tất cả ban đầu với trọng tâm là biểu diễn dữ liệu, có nhiều kiểu truy cập bộ nhớ dự đoán hơn, giảm sử dụng bộ nhớ, đơn giản hóa các luồng điều khiển để vượt qua đồng nhất hơn, giảm số lượng chức năng trong hệ thống của bạn gây ra tác dụng phụ mà không phải chịu chi phí sao chép sâu rất tốn kém, thống nhất API của bạn, v.v.

Khi bạn kết hợp tất cả những điều này, bạn có xu hướng kết thúc với một hệ thống giảm thiểu lượng trạng thái chia sẻ nơi bạn tình cờ tìm thấy một thiết kế thực sự thân thiện để đồng thời. Và nếu bất kỳ trạng thái nào cần được chia sẻ, bạn thường thấy nó không có nhiều tranh cãi khi sử dụng đồng bộ hóa mà không gây ra tắc nghẽn luồng, và hơn nữa, nó thường có thể được xử lý bởi cấu trúc dữ liệu trung tâm của bạn để thống nhất biểu diễn tất cả mọi thứ trong hệ thống để bạn không phải áp dụng đồng bộ hóa luồng cho hàng trăm địa điểm khác nhau, chỉ một số ít.

Bây giờ khi chúng tôi đi sâu vào một trong những thành phần phức tạp hơn như mắt lưới, chúng tôi lặp lại quy trình thiết kế tương tự, bắt đầu bằng việc nghĩ về dữ liệu trước tiên. Và nếu chúng ta làm đúng, chúng ta thậm chí có thể dễ dàng xử lý song song việc xử lý một lưới, nhưng thiết kế kiến ​​trúc rộng hơn mà chúng ta thiết lập đã cho phép chúng ta xử lý song song nhiều lướ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.