Xử lý chính xác các đối tượng khi chấm dứt máy chủ


9

Tôi đang làm việc trên một dự án C ++ lớn. Nó bao gồm một máy chủ hiển thị API REST, cung cấp giao diện đơn giản và thân thiện với người dùng cho một hệ thống rất rộng bao gồm nhiều máy chủ khác. Codebase khá lớn và phức tạp, và phát triển theo thời gian mà không có một thiết kế phù hợp. Nhiệm vụ của tôi là triển khai các tính năng mới và cấu trúc lại / sửa mã cũ để làm cho nó ổn định và đáng tin cậy hơn.

Hiện tại, máy chủ tạo ra một số đối tượng sống lâu không bao giờ bị chấm dứt cũng không được xử lý khi quá trình kết thúc. Điều này làm cho Valgrind gần như không thể sử dụng để phát hiện rò rỉ, vì không thể phân biệt giữa hàng ngàn rò rỉ hợp pháp (nghi vấn) với những rò rỉ "nguy hiểm".

Ý tưởng của tôi là đảm bảo rằng tất cả các đối tượng đều được xử lý trước khi chấm dứt, nhưng khi tôi đưa ra đề xuất này, các đồng nghiệp và sếp của tôi đã phản đối tôi chỉ ra rằng hệ điều hành sẽ giải phóng bộ nhớ đó (điều hiển nhiên đối với mọi người) và xử lý các đối tượng sẽ làm chậm quá trình tắt máy chủ (lúc này, về cơ bản là một cuộc gọi đến std::exit). Tôi đã trả lời rằng có một quy trình tắt "sạch" không nhất thiết có nghĩa là người ta phải sử dụng nó. Chúng tôi luôn có thể gọi std::quick_exithoặc chỉ kill -9là quá trình nếu chúng tôi cảm thấy thiếu kiên nhẫn.

Họ trả lời "hầu hết các trình nền và quy trình Linux không bận tâm giải phóng bộ nhớ khi tắt máy". Mặc dù tôi có thể thấy điều đó, nhưng cũng đúng là dự án của chúng tôi cần gỡ lỗi bộ nhớ chính xác, vì tôi đã thấy hỏng bộ nhớ, giải phóng gấp đôi và các biến chưa được khởi tạo.

Quan điểm của bạn là gì? Tôi đang theo đuổi một nỗ lực vô nghĩa? Nếu không, làm thế nào tôi có thể thuyết phục đồng nghiệp và sếp của tôi? Nếu vậy, tại sao, và tôi nên làm gì thay thế?


Bên cạnh đối số hiệu năng (là hợp lý!), Có phải rất nhiều nỗ lực để cô lập các đối tượng sống lâu và thêm mã dọn dẹp cho chúng?
Doc Brown

Câu trả lời:


7

Thêm một chuyển đổi vào quy trình máy chủ có thể được sử dụng trong các phép đo valgrind sẽ giải phóng tất cả bộ nhớ. Bạn có thể sử dụng công tắc này để thử nghiệm. Tác động sẽ là tối thiểu trong các hoạt động bình thường.

Chúng tôi đã có một quá trình chạy dài, sẽ mất vài phút để phát hành 1000 đối tượng. Sẽ hiệu quả hơn nhiều khi chỉ cần thoát ra và để họ chết. Thật không may, như bạn chỉ ra, điều này gây khó khăn cho việc phát hiện rò rỉ bộ nhớ thực bằng valgrind hoặc bất kỳ công cụ nào khác.

Đây là một sự thỏa hiệp tốt cho thử nghiệm của chúng tôi trong khi không ảnh hưởng đến hiệu suất bình thường.


1
+1 Chủ nghĩa thực dụng FTW. Có giá trị trong đo lường, nhưng cũng có giá trị trong việc tắt máy nhanh chóng.
Ross Patterson

2
Thay thế cho một chuyển đổi dòng lệnh, bạn cũng có thể muốn xem xét việc thực hiện xóa các đối tượng vĩnh viễn bên trong khối #ifdef DEBUG.
Jules

3

Chìa khóa ở đây là đây:

Mặc dù tôi có thể thấy điều đó, nhưng cũng đúng là dự án của chúng tôi cần gỡ lỗi bộ nhớ chính xác, vì tôi đã thấy hỏng bộ nhớ, giải phóng gấp đôi và các biến chưa được khởi tạo.

Điều này khá trực tiếp ngụ ý rằng cơ sở mã của bạn được ghép lại với nhau từ không có gì ngoài hy vọng và chuỗi. Các lập trình viên C ++ có năng lực không có quyền tự do kép.

Bạn hoàn toàn đang theo đuổi một nỗ lực vô nghĩa - như trong, bạn đang giải quyết một triệu chứng nhỏ của vấn đề thực tế, đó là mã của bạn đáng tin cậy như mô-đun dịch vụ Apollo 13.

Nếu bạn lập trình máy chủ của mình chính xác với RAII, những vấn đề này sẽ không xảy ra và vấn đề trong câu hỏi của bạn sẽ được loại bỏ. Ngoài ra, mã của bạn thực sự có thể thực thi chính xác theo thời gian. Vì vậy, nó rõ ràng là lựa chọn tốt nhất.


Chắc chắn vấn đề nằm ở bức tranh lớn hơn. Tuy nhiên, rất hiếm khi người ta có thể tìm thấy các tài nguyên và sự cho phép để cấu trúc lại / viết lại một dự án để hình thành tốt hơn.
Cengiz Can

@CengizCan: Nếu bạn muốn sửa lỗi, bạn cần cấu trúc lại. Đó là cách nó hoạt động.
DeadMG

2

Một cách tiếp cận tốt là thu hẹp cuộc thảo luận với các đồng nghiệp của bạn bằng phương pháp phân loại. Với một cơ sở mã lớn, chắc chắn không có một lý do duy nhất mà là nhiều lý do (có thể xác định) cho các đối tượng sống lâu.

Ví dụ:

  • Đối tượng sống lâu mà không được ai tham khảo (rò rỉ thực sự). Đây là một lỗi logic lập trình. Khắc phục những lỗi có mức độ ưu tiên thấp hơn KHÔNG GIỚI HẠN, chúng chịu trách nhiệm cho dung lượng bộ nhớ của bạn tăng theo thời gian (và làm giảm chất lượng ứng dụng của bạn). Nếu chúng làm cho dấu chân bộ nhớ của bạn tăng theo thời gian, hãy sửa chúng với mức độ ưu tiên cao hơn.

  • Các đối tượng sống lâu, vẫn được tham chiếu nhưng không được sử dụng nữa (do logic chương trình), nhưng không làm cho dấu chân bộ nhớ của bạn tăng lên. Xem lại mã và cố gắng tìm các lỗi khác dẫn đến điều đó. Thêm ý kiến ​​vào cơ sở mã nếu đó là tối ưu hóa (hiệu suất) có chủ ý.

  • Đối tượng sống lâu "theo thiết kế". Mẫu đơn chẳng hạn. Chúng thực sự rất khó để thoát khỏi, đặc biệt nếu đó là một ứng dụng đa luồng.

  • Đối tượng tái chế. Đối tượng sống lâu không cần phải luôn luôn xấu. Họ cũng có thể có lợi. Thay vì có phân bổ / giải quyết bộ nhớ tần số cao, việc thêm các đối tượng hiện không được sử dụng vào một thùng chứa để rút ra khi cần một khối bộ nhớ như vậy một lần nữa có thể giúp tăng tốc ứng dụng và tránh bị phân mảnh. Họ có thể dễ dàng giải phóng vào thời gian tắt máy, có thể trong một bản dựng "thiết bị / kiểm tra" đặc biệt.

  • "Các đối tượng được chia sẻ" - các đối tượng được sử dụng (được tham chiếu) bởi nhiều đối tượng khác và không ai biết chính xác khi nào nó được lưu để giải phóng chúng. Xem xét biến chúng thành các đối tượng đếm tham chiếu.

Một khi bạn đã phân loại các lý do thực sự cho những đối tượng không tham gia đó, việc nhập một trường hợp bằng cách thảo luận trường hợp và tìm giải pháp sẽ dễ dàng hơn nhiều.


0

IMHO, thời gian sống của những vật thể này sẽ không bao giờ được tạo ra và chết đi khi hệ thống ngừng hoạt động. Đây là những biến số toàn cầu, mà tất cả chúng ta đều biết là xấu xấu xấu xấu. Đặc biệt là trong thời đại của con trỏ thông minh, không có lý do để làm điều này ngoài sự lười biếng. Nhưng quan trọng hơn, nó bổ sung một mức nợ kỹ thuật cho hệ thống của bạn mà ai đó có thể phải đối phó vào một ngày nào đó.

Ý tưởng của "nợ kỹ thuật" là khi bạn đi một lối tắt như thế này, khi ai đó muốn thay đổi mã trong tương lai (giả sử, tôi muốn có thể khiến khách hàng chuyển sang "chế độ ngoại tuyến" hoặc "chế độ ngủ ", Hoặc tôi muốn có thể chuyển đổi máy chủ mà không cần khởi động lại quy trình), họ sẽ phải nỗ lực để làm những gì bạn đang bỏ qua. Nhưng họ sẽ duy trì mã của bạn, vì vậy họ sẽ không biết nhiều về nó như bạn, vì vậy họ sẽ mất nhiều thời gian hơn (Tôi không nói chuyện dài hơn 20%, tôi sẽ nói chuyện dài hơn 20 lần!). Ngay cả khi đó là bạn, thì bạn sẽ không làm việc với mã cụ thể này trong nhiều tuần hoặc nhiều tháng và sẽ mất nhiều thời gian hơn để loại bỏ mạng nhện để triển khai chính xác.

Trong trường hợp này, có vẻ như bạn có sự kết hợp rất chặt chẽ giữa đối tượng máy chủ và các đối tượng "tồn tại lâu" ... có những lúc một bản ghi có thể (và nên) tồn tại lâu hơn kết nối với máy chủ. Việc duy trì mọi thay đổi đối với một đối tượng trong máy chủ có thể rất tốn kém, do đó, tốt hơn là các đối tượng được sinh ra thực sự được xử lý đối với máy chủ, với các cuộc gọi lưu và cập nhật thực sự thay đổi máy chủ. Điều này thường được gọi là mẫu hồ sơ hoạt động. Xem:

http://en.wikipedia.org/wiki/Active_record_potype

Trong C ++, tôi sẽ có mỗi bản ghi hoạt động có một yếu_ptr đến máy chủ, có thể ném mọi thứ một cách thông minh nếu kết nối máy chủ bị tối. Các lớp này có thể là dân cư lười biếng hoặc hàng loạt, tùy thuộc vào nhu cầu của bạn, nhưng thời gian tồn tại của các đối tượng đó chỉ nên là nơi chúng được sử dụng.

Xem thêm:

Có lãng phí thời gian để giải phóng tài nguyên trước khi tôi thoát khỏi một quy trình không?

Cai khac


This reeks of global variablesLàm thế nào để bạn đi từ "có hàng ngàn đối tượng cần được giải phóng" đến "chúng phải là toàn cầu"? Đó là một bước nhảy vọt của logic.
Doval

0

Nếu bạn có thể dễ dàng xác định nơi các đối tượng tồn tại vô thời hạn được phân bổ, một lần có thể là phân bổ chúng bằng cơ chế phân bổ thay thế để chúng không xuất hiện trong báo cáo rò rỉ valgrind hoặc chỉ xuất hiện dưới dạng phân bổ duy nhất.

Trong trường hợp bạn không quen với ý tưởng này, đây là một bài viết về cách phân bổ bộ nhớ tùy chỉnh trong c ++ , mặc dù lưu ý rằng giải pháp của bạn có thể đơn giản hơn các ví dụ trong bài viết đó vì bạn không cần phải xử lý xóa. !

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.