2008-12-09 23 views

Trả lời

21

Mã Re-entrant không có trạng thái ở một điểm duy nhất. Bạn có thể gọi mã trong khi một cái gì đó đang thực hiện trong mã. Nếu mã sử dụng trạng thái toàn cục, một cuộc gọi có thể ghi đè lên trạng thái toàn cục, phá vỡ tính toán trong cuộc gọi khác.

Mã an toàn chủ đề là mã không có điều kiện chủng tộc hoặc các vấn đề tương tranh khác. Một điều kiện chủng tộc là nơi mà thứ tự mà trong đó hai chủ đề làm một cái gì đó ảnh hưởng đến tính toán. Một vấn đề đồng thời điển hình là nơi thay đổi cấu trúc dữ liệu được chia sẻ có thể được hoàn thành một phần và để lại trong trạng thái không nhất quán. Để tránh điều này, bạn phải sử dụng các cơ chế kiểm soát đồng thời như các semaphores của mutexes để đảm bảo rằng không có gì khác có thể truy cập cấu trúc dữ liệu cho đến khi hoạt động được hoàn thành. Ví dụ, một đoạn mã có thể không tái nhập nhưng an toàn theo luồng nếu nó được bảo vệ bên ngoài bằng một mutex nhưng vẫn có cấu trúc dữ liệu toàn cục, nơi trạng thái phải nhất quán trong toàn bộ thời lượng của cuộc gọi. Trong trường hợp này, cùng một luồng có thể bắt đầu gọi lại vào quy trình trong khi vẫn được bảo vệ bởi một mutex hạt thô bên ngoài. Nếu cuộc gọi lại xảy ra từ bên trong thủ tục không tái nhập cuộc gọi có thể rời khỏi cấu trúc dữ liệu ở trạng thái có thể phá vỡ tính toán từ quan điểm của người gọi.

Một đoạn mã có thể được đưa lại nhưng không an toàn nếu nó có thể thay đổi phi nguyên tử thành cấu trúc dữ liệu được chia sẻ (và có thể chia sẻ) có thể bị gián đoạn ở giữa bản cập nhật. trong trạng thái không nhất quán. Trong trường hợp này, một luồng khác truy cập vào cấu trúc dữ liệu có thể bị ảnh hưởng bởi cấu trúc dữ liệu được thay đổi một nửa và bị lỗi hoặc thực hiện một thao tác làm hỏng dữ liệu.

+3

Ví dụ thứ hai của bạn không có vẻ mang lại lợi ích cho tôi. Nếu sự thay đổi có thể bị gián đoạn để lại một trạng thái không nhất quán, và một tín hiệu xảy ra tại thời điểm đó, và xử lý cho tín hiệu đó gọi hàm, sau đó thông thường nó sẽ bùng nổ. Đó là vấn đề tái nhập, chứ không phải vấn đề an toàn chỉ. –

+1

Bạn nói đúng - như bạn nói bên dưới, bạn cũng sẽ phải vô hiệu hóa các tín hiệu cho ví dụ sau để có hiệu quả. – ConcernedOfTunbridgeWells

+0

@ConcernedOfTunbridgeWells, nếu một func sử dụng đống bên trong, có một cơ hội tốt mà func này không được tái nhập. Tại sao? – Alcott

8

Bài viết đó cho biết:

"một chức năng có thể là reentrant, thread-safe, cả hai hoặc không."

Nó cũng cho biết:

"Các chức năng không lặp lại không an toàn chủ đề".

Tôi có thể thấy điều này có thể gây ra sự lộn xộn. Chúng có nghĩa là các hàm tiêu chuẩn được ghi nhận là không bắt buộc phải tái nhập cũng không bắt buộc phải an toàn luồng, đúng với các thư viện POSIX iirc (và POSIX khai báo nó cũng đúng với các thư viện ANSI/ISO, ISO có không có khái niệm về chủ đề và do đó không có khái niệm về chủ đề an toàn). Nói cách khác, "nếu một hàm nói rằng nó không phải là reentrant, thì nó đang nói nó cũng không an toàn". Đó không phải là sự cần thiết hợp lý, nó chỉ là một quy ước. Dưới đây là một số mã giả an toàn thread (tốt, có rất nhiều cơ hội cho các cuộc gọi lại để tạo các khóa chết do khóa đảo ngược, nhưng hãy giả sử tài liệu chứa đầy đủ thông tin để người dùng tránh điều đó) nhưng không tham gia lại .Đây là vụ để tăng bộ đếm toàn cầu, và thực hiện các cuộc gọi lại:

take_global_lock(); 
int i = get_global_counter(); 
do_callback(i); 
set_global_counter(i+1); 
release_global_lock(); 

Nếu gọi lại gọi thói quen này một lần nữa, kết quả là gọi lại khác, sau đó cả hai cấp độ của callback sẽ nhận được các thông số tương tự (có thể OK, tùy thuộc vào API), nhưng bộ đếm sẽ chỉ được tăng lên một lần (gần như chắc chắn không phải API bạn muốn, vì vậy nó sẽ bị cấm).

Đó là giả định khóa là đệ quy, tất nhiên. Nếu khóa không đệ quy, thì dĩ nhiên mã không phải là reentrant, vì lấy khóa lần thứ hai sẽ không hoạt động.

Dưới đây là một số mã giả mà là "một cách yếu ớt re-entrant" nhưng không thread-safe:

int i = get_global_counter(); 
do_callback(i); 
set_global_counter(get_global_counter()+1); 

Bây giờ nó là tốt để gọi hàm từ gọi lại, nhưng nó không phải là an toàn để gọi hàm đồng thời từ các chủ đề khác nhau. Nó cũng không an toàn để gọi nó từ một bộ xử lý tín hiệu, bởi vì sự ủy thác lại từ một bộ xử lý tín hiệu cũng có thể phá vỡ số đếm nếu tín hiệu xảy ra vào đúng thời điểm. Vì vậy, mã không được tái nhập theo định nghĩa đúng. Đây là một số mã được cho là hoàn toàn tái nhập (ngoại trừ tôi nghĩ tiêu chuẩn phân biệt giữa reentrant và 'không bị gián đoạn bởi tín hiệu', và tôi không chắc chắn nơi này rơi), nhưng vẫn không phải là thread- an toàn:

int i = get_global_counter(); 
do_callback(i); 
disable_signals(); // and any other kind of interrupts on your system 
set_global_counter(get_global_counter()+1); 
restore_signal_state(); 

Trên ứng dụng đơn luồng, điều này là tốt, giả sử rằng hệ điều hành hỗ trợ vô hiệu hóa mọi thứ cần được tắt. Nó ngăn cản sự tái xuất hiện tại điểm quan trọng. Tùy thuộc vào cách tín hiệu bị vô hiệu hóa, có thể an toàn khi gọi từ trình xử lý tín hiệu, mặc dù trong ví dụ cụ thể này vẫn có sự cố của tham số được chuyển đến cuộc gọi lại giống nhau cho các cuộc gọi riêng biệt. Nó vẫn có thể đi sai đa luồng, mặc dù. Trong thực tế, không an toàn chủ đề thường ngụ ý không tái nhập, vì (không chính thức) bất cứ điều gì có thể đi sai do thread bị gián đoạn bởi bộ lập lịch và chức năng được gọi lại từ một chủ đề khác, cũng có thể đi sai nếu thread bị gián đoạn bởi một tín hiệu, và chức năng được gọi lại từ bộ xử lý tín hiệu. Nhưng sau đó các "sửa chữa" để ngăn chặn các tín hiệu (vô hiệu hóa chúng) là khác nhau từ "sửa chữa" để ngăn ngừa đồng thời (ổ khóa, thường). Đây là nguyên tắc tốt nhất.

Lưu ý rằng tôi đã ngụ ý các hình cầu ở đây, nhưng chính xác những cân nhắc tương tự sẽ áp dụng nếu hàm này lấy làm tham số con trỏ tới bộ đếm và khóa. Nó chỉ là các trường hợp khác nhau sẽ được thread-không an toàn hoặc không tái tham gia khi được gọi với cùng một tham số, chứ không phải là khi được gọi là ở tất cả.