Có một số ví dụ hay ở đây nhưng tôi muốn nhảy vào với một số cá nhân nơi sự bất biến đã giúp ích rất nhiều. Trong trường hợp của tôi, tôi bắt đầu thiết kế một cấu trúc dữ liệu đồng thời bất biến, chủ yếu với hy vọng có thể tự tin chạy mã song song với việc đọc và ghi chồng chéo và không phải lo lắng về điều kiện cuộc đua. Có một cuộc nói chuyện John Carmack đã truyền cảm hứng cho tôi để thực hiện nó khi anh ấy nói về một ý tưởng như vậy. Đây là một cấu trúc khá cơ bản và khá tầm thường để thực hiện như thế này:
Tất nhiên với một vài tiếng chuông và tiếng huýt sáo như nó có thể loại bỏ các yếu tố trong thời gian liên tục và để lại các lỗ có thể thu hồi được và có các khối bị hủy bỏ nếu chúng trở nên trống rỗng và có khả năng được giải phóng trong một trường hợp bất biến nhất định. Nhưng về cơ bản để sửa đổi cấu trúc, bạn sửa đổi phiên bản "tạm thời" và về cơ bản cam kết những thay đổi bạn đã thực hiện để có được một bản sao bất biến mới không chạm vào phiên bản cũ, với phiên bản mới chỉ tạo ra các bản sao mới của các khối mà phải được làm cho độc đáo trong khi sao chép nông và tham chiếu đếm những cái khác.
Tuy nhiên, tôi không tìm thấy nó màhữu ích cho mục đích đa luồng. Rốt cuộc, vẫn còn vấn đề về khái niệm, trong đó, một hệ thống vật lý áp dụng đồng thời vật lý trong khi người chơi đang cố gắng di chuyển các yếu tố xung quanh trong một thế giới. Bạn đi cùng với bản sao bất biến nào của dữ liệu được chuyển đổi, dữ liệu mà người chơi đã chuyển đổi hoặc hệ thống vật lý đã chuyển đổi? Vì vậy, tôi đã không thực sự tìm thấy một giải pháp đơn giản và tốt đẹp cho vấn đề khái niệm cơ bản này ngoại trừ việc có các cấu trúc dữ liệu có thể thay đổi, chỉ khóa theo cách thông minh hơn và không khuyến khích việc đọc và ghi chồng lên các phần giống nhau của bộ đệm để tránh các luồng bị đình trệ. Đó là điều mà John Carmack dường như có thể tìm ra cách giải quyết trong các trò chơi của mình; ít nhất anh ta nói về nó như thể anh ta gần như có thể nhìn thấy một giải pháp mà không cần mở một chiếc xe giun. Tôi đã không đi xa như anh ấy về vấn đề đó. Tất cả những gì tôi có thể thấy là những câu hỏi thiết kế vô tận nếu tôi cố gắng chỉ song song mọi thứ xung quanh bất biến. Tôi ước tôi có thể dành một ngày để nhặt não của anh ấy vì hầu hết những nỗ lực của tôi bắt đầu với những ý tưởng mà anh ấy đã đưa ra.
Tuy nhiên, tôi tìm thấy giá trị to lớn của cấu trúc dữ liệu bất biến này trong các lĩnh vực khác. Tôi thậm chí còn sử dụng nó ngay bây giờ để lưu trữ những hình ảnh thực sự kỳ lạ và thực hiện truy cập ngẫu nhiên cần thêm một số hướng dẫn (dịch chuyển phải và một chút and
cùng với một lớp chỉ dẫn con trỏ), nhưng tôi sẽ đề cập đến các lợi ích bên dưới.
Hệ thống hoàn tác
Một trong những nơi trực tiếp nhất mà tôi thấy được hưởng lợi từ việc này là hệ thống hoàn tác. Hoàn tác mã hệ thống từng là một trong những điều dễ bị lỗi nhất trong khu vực của tôi (ngành công nghiệp FX trực quan), và không chỉ trong các sản phẩm tôi làm việc mà cả các sản phẩm cạnh tranh (hệ thống hoàn tác của chúng cũng không ổn) vì có rất nhiều khác nhau các loại dữ liệu cần lo lắng về việc hoàn tác và làm lại đúng cách (hệ thống thuộc tính, thay đổi dữ liệu lưới, thay đổi shader không dựa trên thuộc tính như hoán đổi với nhau, thay đổi thứ bậc cảnh như thay đổi cha mẹ của trẻ, thay đổi hình ảnh / kết cấu, v.v ... v.v.).
Vì vậy, số lượng mã hoàn tác cần thiết là rất lớn, thường cạnh tranh với số lượng mã thực hiện hệ thống mà hệ thống hoàn tác phải ghi lại các thay đổi trạng thái. Bằng cách dựa vào cấu trúc dữ liệu này, tôi có thể đưa hệ thống hoàn tác xuống như sau:
on user operation:
copy entire application state to undo entry
perform operation
on undo/redo:
swap application state with undo entry
Thông thường mã ở trên sẽ rất kém hiệu quả khi dữ liệu cảnh của bạn kéo dài hàng gigabyte để sao chép toàn bộ. Nhưng cấu trúc dữ liệu này chỉ sao chép những thứ không thay đổi và nó thực sự khiến nó đủ rẻ để lưu trữ một bản sao bất biến của toàn bộ trạng thái ứng dụng. Vì vậy, bây giờ tôi có thể thực hiện các hệ thống hoàn tác dễ dàng như mã ở trên và chỉ tập trung vào việc sử dụng cấu trúc dữ liệu bất biến này để sao chép các phần không thay đổi của trạng thái ứng dụng ngày càng rẻ hơn và rẻ hơn. Kể từ khi tôi bắt đầu sử dụng cấu trúc dữ liệu này, tất cả các dự án cá nhân của tôi đều có hệ thống hoàn tác chỉ sử dụng mẫu đơn giản này.
Bây giờ vẫn còn một số chi phí ở đây. Lần trước tôi đã đo được khoảng 10 kilobyte chỉ để sao chép toàn bộ trạng thái ứng dụng mà không thực hiện bất kỳ thay đổi nào đối với nó (điều này không phụ thuộc vào độ phức tạp của cảnh do cảnh được sắp xếp theo thứ bậc, vì vậy nếu không có gì bên dưới gốc thay đổi, chỉ có gốc được sao chép nông mà không cần phải xuống trẻ em). Đó là xa 0 byte cần thiết cho một hệ thống hoàn tác chỉ lưu trữ deltas. Nhưng với 10 kilobyte hoàn tác trên mỗi thao tác, đó vẫn chỉ là một megabyte trên 100 hoạt động của người dùng. Thêm vào đó, tôi vẫn có khả năng đè bẹp nó xuống trong tương lai nếu cần.
Ngoại lệ-An toàn
Ngoại lệ-an toàn với một ứng dụng phức tạp không phải là vấn đề nhỏ. Tuy nhiên, khi trạng thái ứng dụng của bạn là bất biến và bạn chỉ sử dụng các đối tượng nhất thời để thực hiện các giao dịch thay đổi nguyên tử, thì nó vốn đã an toàn ngoại lệ vì nếu bất kỳ phần nào của mã bị ném, thì tạm thời bị loại bỏ trước khi đưa ra một bản sao bất biến mới . Vì vậy, điều đó làm tầm thường hóa một trong những điều khó nhất mà tôi luôn tìm thấy để có được ngay trong một cơ sở mã C ++ phức tạp.
Quá nhiều người thường chỉ sử dụng các tài nguyên tuân thủ RAII trong C ++ và nghĩ rằng điều đó đủ để an toàn ngoại lệ. Thường thì không, vì một chức năng thường có thể gây ra tác dụng phụ cho các trạng thái vượt ra ngoài các trạng thái cục bộ trong phạm vi của nó. Nói chung, bạn cần bắt đầu xử lý các bộ bảo vệ phạm vi và logic rollback tinh vi trong những trường hợp đó. Cấu trúc dữ liệu này tạo ra nó vì vậy tôi thường không cần bận tâm đến điều đó vì các chức năng không gây ra tác dụng phụ. Họ đang trả lại các bản sao trạng thái ứng dụng bất biến thay vì chuyển đổi trạng thái của ứng dụng.
Chỉnh sửa không phá hủy
Chỉnh sửa không phá hủy về cơ bản là phân lớp / xếp chồng / kết nối các hoạt động với nhau mà không chạm vào dữ liệu gốc của người dùng (chỉ nhập dữ liệu đầu vào và dữ liệu đầu ra mà không chạm vào đầu vào). Việc triển khai thông thường với một ứng dụng hình ảnh đơn giản như Photoshop và có thể không được hưởng lợi nhiều từ cấu trúc dữ liệu này vì nhiều thao tác có thể chỉ muốn chuyển đổi từng pixel của toàn bộ hình ảnh.
Tuy nhiên, với chỉnh sửa lưới không phá hủy, chẳng hạn, rất nhiều thao tác thường chỉ muốn chuyển đổi một phần của lưới. Một hoạt động có thể chỉ muốn di chuyển một số đỉnh ở đây. Một người khác có thể chỉ muốn chia nhỏ một số đa giác ở đó. Ở đây, cấu trúc dữ liệu bất biến giúp một tấn tránh việc phải tạo toàn bộ bản sao của toàn bộ lưới chỉ để trả về một phiên bản mới của lưới với một phần nhỏ của nó đã thay đổi.
Giảm thiểu tác dụng phụ
Với các cấu trúc này trong tay, nó cũng giúp bạn dễ dàng viết các chức năng giảm thiểu tác dụng phụ mà không phải chịu một hình phạt hiệu suất lớn cho nó. Tôi đã thấy mình viết ngày càng nhiều chức năng chỉ trả lại toàn bộ cấu trúc dữ liệu bất biến theo giá trị trong những ngày này mà không phát sinh tác dụng phụ, ngay cả khi điều đó có vẻ hơi lãng phí.
Ví dụ, điển hình là sự cám dỗ để biến đổi một loạt các vị trí có thể là chấp nhận một ma trận và một danh sách các đối tượng và biến đổi chúng theo một kiểu có thể thay đổi. Những ngày này tôi thấy mình vừa trả lại một danh sách mới các đối tượng.
Khi bạn có nhiều chức năng như thế này trong hệ thống của mình mà không gây ra tác dụng phụ, điều đó chắc chắn giúp bạn dễ dàng suy luận về tính đúng đắn của nó cũng như kiểm tra tính chính xác của nó.
Lợi ích của bản sao giá rẻ
Vì vậy, dù sao đi nữa, đây là những lĩnh vực mà tôi thấy sử dụng nhiều nhất trong các cấu trúc dữ liệu bất biến (hoặc cấu trúc dữ liệu liên tục). Tôi cũng có một chút quá nhiệt tình ban đầu và tạo ra một cây bất biến và danh sách liên kết bất biến và bảng băm bất biến, nhưng theo thời gian, tôi hiếm khi tìm thấy nhiều sử dụng cho những thứ đó. Tôi chủ yếu tìm thấy việc sử dụng hầu hết các container giống như mảng bất biến chunky trong sơ đồ trên.
Tôi cũng vẫn còn rất nhiều mã làm việc với các biến đổi (tìm thấy nó là một điều cần thiết thực tế ít nhất là đối với mã cấp thấp), nhưng trạng thái ứng dụng chính là một hệ thống phân cấp bất biến, đi sâu từ một cảnh bất biến đến các thành phần bất biến bên trong nó. Một số thành phần rẻ hơn vẫn được sao chép đầy đủ, nhưng những thành phần đắt nhất như mắt lưới và hình ảnh sử dụng cấu trúc bất biến để cho phép những bản sao rẻ tiền một phần chỉ những phần cần chuyển đổi.
ConcurrentModificationException
thường được gây ra bởi cùng một luồng làm thay đổi bộ sưu tập trong cùng một luồng, trong phần thân của mộtforeach
vòng lặp trên cùng một bộ sưu tập.