2014-05-17 15 views
5

Một số phương pháp hay để ngăn chặn điều kiện đua xe trong Go? Người duy nhất tôi có thể nghĩ là không chia sẻ dữ liệu giữa goroutines - goroutine mẹ gửi một bản sao sâu của một đối tượng chứ không phải chính đối tượng, vì vậy goroutine con không thể biến đổi cái gì mà cha mẹ có thể. Điều này sẽ sử dụng nhiều bộ nhớ heap hơn, nhưng cách khác là tìm hiểu Haskell: PGolang: tránh điều kiện đua xe

Chỉnh sửa: cũng có trường hợp nào mà phương pháp tôi mô tả ở trên vẫn có thể chạy vào điều kiện chủng tộc không?

+1

Điều kiện chủng tộc khó gỡ lỗi, cũng khó tránh được chúng. Tôi sẽ đề nghị suy nghĩ về các mô hình được mô tả ở đây bởi Rob Pike http://vimeo.com/49718712 khi anh ta đi qua các cấu trúc nhất định về cách các kênh và ngữ nghĩa CSP làm cho ứng dụng an toàn bằng thiết kế thay vì lo lắng về tất cả các vấn đề liên quan đến mutex. Tôi xin lỗi nếu điều này không trả lời câu hỏi của bạn nhưng tôi hy vọng nó sẽ mở ra nhiều cánh cửa hơn cho những ý tưởng mới. – ymg

+0

Cảm ơn bạn đã liên kết. Đúng, điều kiện chủng tộc rất khó để ngăn chặn, nhưng thậm chí khó khăn hơn để gỡ lỗi! Tôi đoán một phiên bản bất biến của các bộ sưu tập sẽ làm giảm bớt vấn đề ở một mức độ nào đó, nhưng sau đó chúng tôi trở lại sân sau của chương trình chức năng. – tldr

+0

Có _are_ các ngôn ngữ dựa trên bất biến/chức năng khác (chẳng hạn như F #), nhưng nó thực sự về phong cách. Bạn đúng về việc cần thêm bộ nhớ, nhưng thiết kế phù hợp có thể loại bỏ một số chi phí. Ví dụ, các mảng ghi trên sửa đổi có thể được thực hiện trong không gian 'n log n' (hoặc ít hơn đối với một số trường hợp tiềm năng) trung bình, chứ không phải là một' 2n' ngây thơ. Mặc dù hỗ trợ ngôn ngữ/trình tối ưu hóa có thể thực hiện một số điều điên rồ cho các ngôn ngữ được xây dựng ... –

Trả lời

13

Điều kiện chủng tộc chắc chắn vẫn tồn tại ngay cả với cấu trúc dữ liệu chưa được chia sẻ. Hãy xem xét những điều sau đây:

B asks A for the currentCount 
C asks A for the currentCount 
B sends A (newDataB, currentCount + 1) 
A stores newDataB at location currentCount+1 
C sends A (newDataC, currentCount + 1) 
A stores newDataC at currentCount + 1 (overwriting newDataB; race condition) 

tình trạng đua này đòi hỏi nhà nước có thể thay đổi tư nhân ở A, nhưng không thể thay đổi cấu trúc dữ liệu được chia sẻ và thậm chí không đòi hỏi nhà nước có thể thay đổi trong B hoặc C. Không có gì là B hoặc C có thể làm gì để ngăn chặn điều kiện chủng tộc này mà không hiểu hợp đồng mà A đưa ra.

Ngay cả Haskell cũng có thể phải chịu đựng các điều kiện chủng tộc này ngay khi tiểu bang bước vào phương trình và trạng thái rất khó loại bỏ hoàn toàn khỏi hệ thống thực. Cuối cùng, bạn muốn chương trình của bạn tương tác với thực tế, và thực tế là trạng thái bình thường.Wikipedia cung cấp cho a helpful race condition example in Haskell sử dụng STM.

Tôi đồng ý rằng cấu trúc dữ liệu không thay đổi tốt có thể làm mọi thứ dễ dàng hơn (Go không thực sự có chúng). Các bản sao đột biến trao đổi một vấn đề cho người khác. Bạn không thể vô tình thay đổi dữ liệu của người khác. Mặt khác, bạn có thể nghĩ rằng rằng bạn đang thay đổi thực tế, khi bạn thực sự chỉ thay đổi một bản sao, dẫn đến một loại lỗi khác. Bạn phải hiểu hợp đồng theo cách nào đó.

Nhưng cuối cùng, Go có xu hướng theo lịch sử của C trên đồng thời: bạn tạo một số quy tắc quyền sở hữu cho mã của mình (như @ tux21b phiếu mua hàng) và đảm bảo bạn luôn theo dõi chúng. mọi việc đều tuyệt vời, và nếu bạn từng phạm sai lầm, thì rõ ràng đó là lỗi của bạn, không phải ngôn ngữ.

(Đừng hiểu sai, tôi thích Go, khá nhiều và thực sự cung cấp một số công cụ tốt để tạo sự đồng thời dễ dàng. Nó không cung cấp nhiều công cụ ngôn ngữ để giúp đồng thời chính xác. Điều đó nói rằng, câu trả lời của tux21b cung cấp rất nhiều lời khuyên tốt, và máy dò cuộc đua chắc chắn là một công cụ mạnh mẽ để giảm các điều kiện chủng tộc, nó không phải là một phần của ngôn ngữ, và nó là về thử nghiệm, chứ không phải chính xác; điều tương tự)

EDIT:. với câu hỏi về việc tại sao cấu trúc dữ liệu bất biến làm cho mọi việc dễ dàng hơn, đây là phần mở rộng của điểm ban đầu của bạn: tạo ra một hợp đồng mà nhiều bên không thay đổi dat cùng một cấu trúc. Nếu cấu trúc dữ liệu là không thay đổi, thì điều đó xuất hiện miễn phí…

Nhiều ngôn ngữ có tập hợp các lớp và lớp học không thay đổi phong phú. C++ cho phép bạn const mọi thứ. Mục tiêu-C có các bộ sưu tập bất biến với các lớp con có thể thay đổi (tạo ra một tập các mẫu khác với const). Scala có các phiên bản riêng biệt có thể thay đổi và bất biến của nhiều loại bộ sưu tập và thực tế phổ biến là sử dụng các phiên bản không thể thay đổi được. Tuyên bố bất biến trong một chữ ký phương pháp là một dấu hiệu quan trọng của hợp đồng.

Khi bạn vượt qua một []byte thành goroutine, không có cách nào để biết từ mã cho dù goroutine có ý định sửa đổi lát hay không khi bạn có thể tự sửa đổi lát. Có một mẫu mới nổi, nhưng chúng giống như quyền sở hữu đối tượng C++ trước khi di chuyển ngữ nghĩa; rất nhiều cách tiếp cận tốt đẹp, nhưng không có cách nào để biết cái nào đang được sử dụng. Đó là một điều quan trọng mà mọi chương trình cần phải làm một cách chính xác, nhưng ngôn ngữ cho bạn không có công cụ tốt, và không có mô hình phổ quát được sử dụng bởi các nhà phát triển.

+1

Đây là một câu trả lời tuyệt vời. Bạn có thể xây dựng một chút về cách cấu trúc dữ liệu bất biến làm cho mọi thứ dễ dàng hơn không? – tldr

6

Đi không thực thi an toàn bộ nhớ tĩnh. Có một số cách để xử lý vấn đề ngay cả trong các cơ sở mã lớn, nhưng tất cả chúng đòi hỏi sự chú ý của bạn.

  • Bạn có thể gửi con trỏ xung quanh, nhưng một thành ngữ phổ biến là báo hiệu việc chuyển quyền sở hữu bằng cách gửi con trỏ. Ví dụ: khi bạn chuyển con trỏ của đối tượng đến một Goroutine khác, bạn không chạm vào nó một lần nữa, trừ khi bạn lấy lại đối tượng từ goroutine đó (hoặc bất kỳ Goroutine nào khác nếu đối tượng được truyền qua vài lần) qua một tín hiệu khác.

  • Nếu dữ liệu của bạn được nhiều người dùng chia sẻ và không thay đổi thường xuyên, bạn có thể chia sẻ con trỏ tới dữ liệu đó trên toàn cầu và cho phép mọi người đọc từ đó. Nếu một Goroutine muốn thay đổi nó, nó cần phải làm theo các thành ngữ sao chép-trên-ghi, tức là sao chép đối tượng, thay đổi dữ liệu, cố gắng đặt con trỏ đến đối tượng mới bằng cách sử dụng một cái gì đó như atomic.CompareAndSwap.

  • Sử dụng Mutex (hoặc RWMutex nếu bạn muốn cho phép nhiều người đọc đồng thời cùng một lúc) không phải là xấu. Chắc chắn, một Mutex không có viên đạn bạc và nó thường không phù hợp để thực hiện đồng bộ hóa (và nó bị lạm dụng trong nhiều ngôn ngữ dẫn đến danh tiếng kém), nhưng đôi khi nó là giải pháp đơn giản và hiệu quả nhất.

Có thể có nhiều cách khác. Chỉ gửi các giá trị bằng cách sao chép chúng là một cách khác và dễ xác minh, nhưng tôi nghĩ bạn không nên giới hạn bản thân mình với phương thức này. Tất cả chúng ta đều trưởng thành và tất cả chúng ta đều có thể đọc tài liệu (giả sử bạn viết mã đúng cách).

Công cụ Go cũng đi kèm với một giá trị race detector được tích hợp sẵn, có thể phát hiện các cuộc đua khi chạy. Viết rất nhiều bài kiểm tra và thực hiện chúng với trình phát hiện cuộc đua được kích hoạt và coi trọng từng thông báo lỗi. Họ thường chỉ ra một thiết kế xấu hoặc phức tạp.

(PS: Bạn có thể muốn xem Rust nếu bạn muốn một trình biên dịch và loại hệ thống có thể xác minh truy cập đồng thời trong thời gian biên dịch, trong khi vẫn cho phép chia sẻ trạng thái. ý tưởng trông khá hứa hẹn.)

+0

Đây là tất cả các điểm hợp lệ và chúng sẽ hoạt động nếu tôi đã tự viết toàn bộ mã. Nhưng, nếu goroutine của tôi đang chạy một hàm từ thư viện bên ngoài, tôi sẽ phải trải qua quá trình triển khai của nó để đảm bảo rằng nhà phát triển thư viện cũng tuân theo hợp đồng.Tôi nghĩ rằng không chia sẻ bất kỳ trạng thái nào làm cho nó dễ dàng hơn nhiều để cách nhiệt bản thân mình từ bất kỳ bên ngoài. Có kịch bản nào mà giải pháp đề xuất của tôi vẫn có thể chạy vào điều kiện cuộc đua không? – tldr

Các vấn đề liên quan