Java và C # cung cấp sự an toàn cho bộ nhớ bằng cách kiểm tra giới hạn mảng và các kết xuất con trỏ.
Những cơ chế nào có thể được thực hiện thành ngôn ngữ lập trình để ngăn chặn khả năng xảy ra tình trạng chủng tộc và bế tắc?
Java và C # cung cấp sự an toàn cho bộ nhớ bằng cách kiểm tra giới hạn mảng và các kết xuất con trỏ.
Những cơ chế nào có thể được thực hiện thành ngôn ngữ lập trình để ngăn chặn khả năng xảy ra tình trạng chủng tộc và bế tắc?
Câu trả lời:
Các cuộc đua xảy ra khi bạn có bí danh đồng thời của một đối tượng và, ít nhất, một trong những bí danh đang biến đổi.
Vì vậy, để ngăn chặn các cuộc đua, bạn cần phải thực hiện một hoặc nhiều điều kiện không đúng sự thật.
Phương pháp khác nhau giải quyết các khía cạnh khác nhau. Lập trình hàm nhấn mạnh tính bất biến giúp loại bỏ tính đột biến. Khóa / nguyên tử loại bỏ tính đồng thời. Các loại affine loại bỏ răng cưa (Rust loại bỏ răng cưa có thể thay đổi). Mô hình diễn viên thường loại bỏ răng cưa.
Bạn có thể hạn chế các đối tượng có thể được đặt bí danh để dễ dàng hơn để đảm bảo tránh các điều kiện trên. Đó là nơi các kênh và / hoặc kiểu truyền tin nhắn đến. Bạn không thể bí danh bộ nhớ tùy ý, chỉ là phần cuối của kênh hoặc hàng đợi được sắp xếp để không có cuộc đua. Thông thường bằng cách tránh tính đồng thời tức là khóa hoặc nguyên tử.
Nhược điểm của các cơ chế khác nhau là chúng hạn chế các chương trình mà bạn có thể viết. Càng hạn chế cùn, các chương trình càng ít. Vì vậy, không có răng cưa hoặc không có khả năng biến đổi, và dễ dàng để lý do, nhưng rất hạn chế.
Đó là lý do tại sao Rust gây ra sự khuấy động như vậy. Đó là một ngôn ngữ kỹ thuật (so với ngôn ngữ học thuật) hỗ trợ răng cưa và khả năng biến đổi nhưng có trình biên dịch kiểm tra rằng chúng không xảy ra đồng thời. Mặc dù không phải là lý tưởng, nhưng nó cho phép một lớp chương trình lớn hơn được viết an toàn hơn rất nhiều so với những người tiền nhiệm của nó.
Java và C # cung cấp sự an toàn cho bộ nhớ bằng cách kiểm tra giới hạn mảng và các kết xuất con trỏ.
Điều quan trọng trước tiên là nghĩ về cách C # và Java làm điều này. Họ làm như vậy bằng cách chuyển đổi hành vi không xác định trong C hoặc C ++ thành hành vi được xác định: làm hỏng chương trình . Không bao giờ có thể bắt gặp các ngoại lệ và chỉ mục mảng Null trong một chương trình C # hoặc Java chính xác; chúng không nên xảy ra ở nơi đầu tiên vì chương trình không nên có lỗi đó.
Nhưng đó là tôi nghĩ không có ý gì với câu hỏi của bạn! Chúng tôi hoàn toàn có thể dễ dàng viết một thời gian chạy "bế tắc an toàn" kiểm tra định kỳ để xem liệu có n luồng nào đang chờ nhau và chấm dứt chương trình nếu điều đó xảy ra, nhưng tôi không nghĩ rằng điều đó sẽ làm bạn hài lòng.
Những cơ chế nào có thể được thực hiện thành ngôn ngữ lập trình để ngăn chặn khả năng xảy ra tình trạng chủng tộc và bế tắc?
Vấn đề tiếp theo chúng tôi gặp phải với câu hỏi của bạn là "điều kiện chủng tộc", không giống như những bế tắc, rất khó phát hiện. Hãy nhớ rằng, những gì chúng ta sau khi an toàn trong luồng không phải là loại bỏ các cuộc đua . Những gì chúng tôi theo sau là làm cho chương trình chính xác cho dù ai là người chiến thắng trong cuộc đua ! Vấn đề với điều kiện cuộc đua không phải là hai luồng đang chạy theo thứ tự không xác định và chúng tôi không biết ai sẽ hoàn thành trước. Vấn đề với các điều kiện chủng tộc là các nhà phát triển quên rằng một số đơn hàng hoàn thiện luồng là có thể và không tính đến khả năng đó.
Vì vậy, câu hỏi của bạn về cơ bản sôi nổi đến "có một cách mà một ngôn ngữ lập trình có thể đảm bảo rằng chương trình của tôi là chính xác?" và câu trả lời cho câu hỏi đó là, trong thực tế, không.
Cho đến nay tôi chỉ phê bình câu hỏi của bạn. Hãy để tôi thử chuyển đổi bánh răng ở đây và giải quyết tinh thần của câu hỏi của bạn. Có sự lựa chọn nào mà các nhà thiết kế ngôn ngữ có thể đưa ra để giảm thiểu tình trạng khủng khiếp mà chúng ta gặp phải với đa luồng không?
Tình hình thực sự là khủng khiếp! Bắt mã đa luồng chính xác, đặc biệt là trên các kiến trúc mô hình bộ nhớ yếu, là rất, rất khó. Đó là hướng dẫn để suy nghĩ về lý do tại sao nó khó khăn:
Vì vậy, có một cách rõ ràng rằng các nhà thiết kế ngôn ngữ có thể làm cho mọi thứ tốt hơn. Bỏ qua các chiến thắng hiệu suất của bộ xử lý hiện đại . Làm cho tất cả các chương trình, ngay cả những chương trình đa luồng, có một mô hình bộ nhớ cực kỳ mạnh mẽ. Điều này sẽ làm cho các chương trình đa luồng chậm hơn nhiều lần, hoạt động trực tiếp với lý do có các chương trình đa luồng ở vị trí đầu tiên: để cải thiện hiệu suất.
Ngay cả việc bỏ qua mô hình bộ nhớ, vẫn có những lý do khác khiến việc đa luồng trở nên khó khăn:
Đó là điểm cuối cùng giải thích thêm. Theo "composable", ý tôi là như sau:
Giả sử chúng ta muốn tính một số nguyên cho trước. Chúng tôi viết một triển khai chính xác của tính toán:
int F(double x) { correct implementation here }
Giả sử chúng ta muốn tính một chuỗi đã cho int:
string G(int y) { correct implementation here }
Bây giờ nếu chúng ta muốn tính một chuỗi được nhân đôi:
double d = whatever;
string r = G(F(d));
G và F có thể được tạo thành một giải pháp chính xác cho vấn đề phức tạp hơn.
Nhưng ổ khóa không có tài sản này vì bế tắc. Một phương thức đúng M1 có các khóa theo thứ tự L1, L2 và một phương thức đúng M2 lấy các khóa theo thứ tự L2, L1, không thể được sử dụng trong cùng một chương trình mà không tạo ra một chương trình không chính xác. Khóa làm cho nó sao cho bạn không thể nói "mọi phương pháp riêng lẻ đều đúng, vì vậy toàn bộ điều này là chính xác".
Vì vậy, những gì chúng ta có thể làm, là nhà thiết kế ngôn ngữ?
Đầu tiên, đừng đến đó. Nhiều luồng điều khiển trong một chương trình là một ý tưởng tồi và chia sẻ bộ nhớ qua các luồng là một ý tưởng tồi, vì vậy đừng đặt nó vào ngôn ngữ hoặc thời gian chạy ở vị trí đầu tiên.
Đây rõ ràng là một người không bắt đầu.
Sau đó, chúng ta hãy chú ý đến câu hỏi cơ bản hơn: tại sao chúng ta có nhiều luồng ở vị trí đầu tiên? Có hai lý do chính, và chúng được đưa vào cùng một điều thường xuyên, mặc dù chúng rất khác nhau. Họ bị giới hạn bởi vì cả hai đều về quản lý độ trễ.
Ý kiến tồi. Thay vào đó, sử dụng không đồng bộ luồng đơn thông qua coroutines. C # làm điều này đẹp. Java, không tốt lắm. Nhưng đây là cách chính mà các nhà thiết kế ngôn ngữ hiện tại đang giúp giải quyết vấn đề luồng. Các await
nhà điều hành trong C # (lấy cảm hứng từ F # công việc không đồng bộ và tình trạng kỹ thuật khác) được đưa vào ngày càng nhiều ngôn ngữ.
Các nhà thiết kế ngôn ngữ có thể giúp đỡ bằng cách tạo ra các tính năng ngôn ngữ hoạt động tốt với tính song song. Ví dụ, hãy suy nghĩ về cách LINQ được mở rộng một cách tự nhiên đến PLINQ. Nếu bạn là một người nhạy cảm và bạn giới hạn các hoạt động TPL của mình ở các hoạt động gắn với CPU rất song song và không chia sẻ bộ nhớ, bạn có thể nhận được những chiến thắng lớn tại đây.
Ta còn làm gì khác được nữa?
C # không cho phép bạn chờ đợi trong khóa, vì đó là công thức cho những bế tắc. C # không cho phép bạn khóa loại giá trị vì đó luôn là điều sai; bạn khóa trên hộp, không phải giá trị. C # cảnh báo bạn nếu bạn bí danh một biến động, bởi vì bí danh không áp đặt ngữ nghĩa thu nhận / phát hành. Có rất nhiều cách khác mà trình biên dịch có thể phát hiện các vấn đề phổ biến và ngăn chặn chúng.
C # và Java đã tạo ra một lỗi thiết kế rất lớn bằng cách cho phép bạn sử dụng bất kỳ đối tượng tham chiếu nào làm màn hình. Điều đó khuyến khích tất cả các loại thực hành xấu khiến cho việc theo dõi các bế tắc trở nên khó khăn hơn và khó ngăn chặn chúng một cách tĩnh tại. Và nó lãng phí byte trong mọi tiêu đề đối tượng. Màn hình nên được yêu cầu bắt nguồn từ một lớp màn hình.
STM là một ý tưởng hay và tôi đã chơi xung quanh với các triển khai đồ chơi trong Haskell; nó cho phép bạn soạn thảo các giải pháp chính xác từ các bộ phận chính xác một cách thanh lịch hơn nhiều so với các giải pháp dựa trên khóa. Tuy nhiên, tôi không biết đủ về các chi tiết để nói tại sao nó không thể được thực hiện để hoạt động ở quy mô; Hãy hỏi Joe Duffy lần sau khi bạn gặp anh ấy.
Đã có rất nhiều nghiên cứu về các ngôn ngữ dựa trên tính toán quá trình và tôi không hiểu rõ về không gian đó; hãy thử đọc một vài bài viết về nó và xem bạn có hiểu biết gì không.
Sau khi tôi làm việc tại Microsoft trên Roslyn, tôi đã làm việc tại Coverity và một trong những điều tôi đã làm là lấy giao diện người dùng phân tích bằng Roslyn. Bằng cách có một phân tích từ vựng, cú pháp và ngữ nghĩa chính xác do Microsoft cung cấp, sau đó chúng tôi có thể tập trung vào công việc khó khăn trong việc viết các trình phát hiện tìm thấy các vấn đề đa luồng phổ biến.
Một lý do cơ bản tại sao chúng ta có chủng tộc và bế tắc và tất cả những thứ đó là bởi vì chúng ta đang viết các chương trình nói phải làm gì , và hóa ra tất cả chúng ta đều tào lao khi viết các chương trình cấp bách; máy tính làm những gì bạn nói với nó, và chúng tôi bảo nó làm những điều sai trái. Nhiều ngôn ngữ lập trình hiện đại ngày càng nói nhiều về lập trình khai báo: nói kết quả bạn muốn và để trình biên dịch tìm ra cách hiệu quả, an toàn, chính xác để đạt được kết quả đó. Một lần nữa, hãy nghĩ về LINQ; chúng tôi muốn bạn nói from c in customers select c.FirstName
, trong đó thể hiện một ý định . Hãy để trình biên dịch tìm ra cách viết mã.
Các thuật toán học máy tốt hơn ở một số nhiệm vụ so với các thuật toán được mã hóa bằng tay, mặc dù tất nhiên có nhiều sự đánh đổi bao gồm tính chính xác, thời gian để đào tạo, sự thiên vị được đưa ra bởi đào tạo xấu, v.v. Nhưng có thể là rất nhiều nhiệm vụ mà chúng ta hiện đang viết mã "bằng tay" sẽ sớm có thể tuân theo các giải pháp do máy tạo ra. Nếu con người không viết mã, họ sẽ không viết lỗi.
Xin lỗi đó là một chút lan man ở đó; đây là một chủ đề lớn và khó khăn và không có sự đồng thuận rõ ràng nào xuất hiện trong cộng đồng PL trong 20 năm tôi đã theo dõi tiến bộ trong không gian vấn đề này.