2009-05-13 26 views
75

Gần đây, tôi đã hỏi một câu hỏi, với tiêu đề là "Is malloc thread safe?", và bên trong đó tôi hỏi, "Có phải là người dự thi lại không?"Threadsafe vs re-entrant

Tôi đã ấn tượng rằng tất cả người tham gia lại đều an toàn chỉ.

Giả định này có sai không? chức năng

Trả lời

38

Tái ký dự thi không dựa trên các biến toàn cầu được tiếp xúc trong các tiêu đề thư viện C .. mất strtok() vs strtok_r() ví dụ như trong C.

Một số chức năng cần có một nơi để lưu trữ một ' công việc đang tiến hành ', các hàm lại cho phép bạn chỉ định con trỏ này trong bộ nhớ riêng của luồng, chứ không phải trong toàn cầu. Vì bộ nhớ này độc quyền với chức năng gọi, nó có thể bị gián đoạn và được nhập lại (tái nhập) và vì trong hầu hết các trường hợp, loại trừ lẫn nhau ngoài chức năng thực hiện là không cần thiết để hoạt động, chúng thường được xem xét là chủ đề an toàn. Tuy nhiên, điều này không được bảo đảm bởi định nghĩa.

errno, tuy nhiên, là một trường hợp hơi khác nhau trên hệ thống POSIX (và có xu hướng trở thành kỳ quặc trong bất kỳ giải thích về cách này tất cả các công trình) :)

Nói tóm lại, lõm thường nghĩa chủ đề an toàn (như trong "sử dụng phiên bản reentrant của chức năng đó nếu bạn đang sử dụng chủ đề"), nhưng chủ đề an toàn không phải lúc nào cũng có nghĩa là người tham gia lại (hoặc ngược lại). Khi bạn xem xét an toàn luồng, đồng thời là những gì bạn cần suy nghĩ. Nếu bạn phải cung cấp một phương tiện khóa và loại trừ lẫn nhau để sử dụng một hàm, thì hàm này không phải là chủ đề an toàn.

Nhưng, không phải tất cả các chức năng cũng cần được kiểm tra. malloc() không cần phải reentrant, nó không phụ thuộc vào bất cứ điều gì trong phạm vi của các điểm nhập cảnh cho bất kỳ chủ đề nhất định (và là chính nó thread an toàn).

Các hàm trả về giá trị được phân bổ tĩnh là không chủ đề an toàn mà không sử dụng mutex, futex hoặc cơ chế khóa nguyên tử khác. Tuy nhiên, họ không cần phải reentrant nếu họ sẽ không bị gián đoạn.

tức là .:

static char *foo(unsigned int flags) 
{ 
    static char ret[2] = { 0 }; 

    if (flags & FOO_BAR) 
    ret[0] = 'c'; 
    else if (flags & BAR_FOO) 
    ret[0] = 'd'; 
    else 
    ret[0] = 'e'; 

    ret[1] = 'A'; 

    return ret; 
} 

Vì vậy, như bạn thấy, có nhiều chủ đề sử dụng rằng nếu không có một số loại khóa sẽ là một thảm họa .. nhưng nó không có mục đích là re-entrant. Bạn sẽ chạy vào đó khi bộ nhớ được cấp phát động là điều cấm kỵ trên một số nền tảng được nhúng.

Trong lập trình hoàn toàn chức năng, lõm thường không hàm ý chủ đề an toàn, nó sẽ phụ thuộc vào hành vi của chức năng xác định hoặc nặc danh thông qua với điểm vào chức năng, đệ quy, vv

Cách tốt hơn để đặt 'an toàn chủ đề' là an toàn để truy cập đồng thời, minh họa tốt hơn nhu cầu.

+2

lõm nào không bao hàm thread-safe. Các hàm thuần túy ngụ ý sự an toàn của luồng. –

+0

Câu trả lời tuyệt vời Tim. Chỉ cần để làm rõ, sự hiểu biết của tôi từ "thường xuyên" của bạn là thread-safe không ngụ ý reentrant, nhưng cũng reentrant không ngụ ý thread-an toàn. Bạn sẽ có thể tìm thấy một ví dụ về một chức năng reentrant đó là * không * thread-an toàn? – Riccardo

+0

@ Tim Post "Trong ngắn hạn, reentrant thường có nghĩa là thread an toàn (như trong" sử dụng phiên bản reentrant của chức năng đó nếu bạn đang sử dụng chủ đề "), nhưng thread an toàn không phải lúc nào cũng có nghĩa là tái nhập cảnh." qt [nói] (http://qt-project.org/doc/qt-4.8/threads-reentrancy.html) đối diện: "Do đó, một hàm an toàn chủ đề luôn luôn là reentrant, nhưng một chức năng reentrant không phải luôn luôn thread- an toàn. " – 4pie0

53

Tùy thuộc vào định nghĩa.Ví dụ Qt uses sau:

  • Một thread-safe * chức năng có thể được gọi cùng một lúc từ nhiều chủ đề, ngay cả khi lời gọi sử dụng dữ liệu được chia sẻ, bởi vì tất cả các tham chiếu tới dữ liệu được chia sẻ serialized.

  • Một reentrant chức năng cũng có thể được gọi là đồng thời từ nhiều chủ đề, nhưng chỉ khi mỗi lời gọi sử dụng dữ liệu riêng của mình.

Do đó, một chức năng thread-safe luôn là lõm, nhưng một reentrant chức năng không phải lúc nào cũng thread-safe.

Mở rộng, một lớp được gọi là reentrant nếu các chức năng thành viên của nó có thể được gọi an toàn từ nhiều chủ đề, miễn là mỗi chủ đề sử dụng một thể hiện khác của lớp. Lớp này là thread-safe nếu các hàm thành viên của nó có thể được gọi một cách an toàn từ nhiều luồng, ngay cả khi tất cả các chủ đề sử dụng cùng một thể hiện của lớp đó.

nhưng họ cũng cảnh báo:

Lưu ý: Thuật ngữ trong lĩnh vực xử lý đa luồng không hoàn toàn được chuẩn hóa. POSIX sử dụng định nghĩa về reentrant và thread-safe có phần khác biệt đối với các API C của nó. Khi sử dụng các thư viện lớp C++ hướng đối tượng khác với Qt, hãy chắc chắn rằng các định nghĩa được hiểu.

+1

Định nghĩa về reentrant này quá mạnh. – qweruiop

+4

Downvote. Một hàm an toàn thread KHÔNG phải lúc nào cũng reentrant. –

+0

Một chức năng là cả reentrant và thread-safe nếu nó không sử dụng bất kỳ var toàn cục/tĩnh nào. Chủ đề - an toàn: khi nhiều chủ đề chạy chức năng của bạn cùng một lúc, có cuộc đua nào không ?? Nếu bạn sử dụng var toàn cầu, hãy sử dụng khóa để bảo vệ nó. vì vậy nó an toàn chỉ. reentrant: nếu một tín hiệu xảy ra trong quá trình thực thi chức năng của bạn, và gọi chức năng của bạn trong tín hiệu một lần nữa, nó có an toàn không ??? trong trường hợp này, không có nhiều luồng. Tốt nhất là bạn không sử dụng bất kỳ var tĩnh/global để làm cho nó reentrant, hoặc như trong ví dụ 3. –

42

TL; DR: Một chức năng có thể được reentrant, thread-safe, cả hai hoặc không.

Bài viết trên Wikipedia dành cho thread-safetyreentrancy cũng đáng đọc. Dưới đây là một vài trích dẫn:

Một chức năng là thread-safe nếu:

nó chỉ thao tác chia sẻ cấu trúc dữ liệu trong một cách đảm bảo thực hiện an toàn bằng nhiều đề cùng một lúc.

Một chức năng là reentrant nếu:

nó có thể bị gián đoạn tại bất kỳ điểm nào trong quá trình thực hiện của nó và sau đó một cách an toàn được gọi là một lần nữa ("tái nhập") trước khi lời gọi trước đây của nó hoàn toàn thực hiện .

Như ví dụ về khả năng tái phát, Wikipedia đưa ra ví dụ về một hàm được thiết kế để gọi bởi ngắt hệ thống: giả sử nó đang chạy khi ngắt khác xảy ra. Nhưng đừng nghĩ rằng bạn đang an toàn chỉ vì bạn không mã với ngắt hệ thống: bạn có thể có vấn đề reentrance trong một chương trình đơn luồng nếu bạn sử dụng gọi lại hoặc chức năng đệ quy.

Chìa khóa để tránh nhầm lẫn là reentrant đề cập đến chỉ thực hiện một chuỗi. Đó là một khái niệm từ thời điểm khi không có hệ điều hành đa nhiệm nào tồn tại.

Ví dụ

(Hơi sửa đổi từ các bài viết Wikipedia)

Ví dụ 1: không thread-safe, không reentrant

/* As this function uses a non-const global variable without 
    any precaution, it is neither reentrant nor thread-safe. */ 

int t; 

void swap(int *x, int *y) 
{ 
    t = *x; 
    *x = *y; 
    *y = t; 
} 

Ví dụ 2: thread-safe , không reentrant

/* We use a thread local variable: the function is now 
    thread-safe but still not reentrant (within the 
    same thread). */ 

__thread int t; 

void swap(int *x, int *y) 
{ 
    t = *x; 
    *x = *y; 
    *y = t; 
} 

Ví dụ 3: không thread-safe, reentrant

/* We save the global state in a local variable and we restore 
    it at the end of the function. The function is now reentrant 
    but it is not thread safe. */ 

int t; 

void swap(int *x, int *y) 
{ 
    int s; 
    s = t; 
    t = *x; 
    *x = *y; 
    *y = t; 
    t = s; 
} 

Ví dụ 4: thread-safe, reentrant

/* We use a local variable: the function is now 
    thread-safe and reentrant, we have ascended to 
    higher plane of existence. */ 

void swap(int *x, int *y) 
{ 
    int t; 
    t = *x; 
    *x = *y; 
    *y = t; 
} 
+3

Tôi biết tôi không phải bình luận chỉ để nói cảm ơn, nhưng đây là một trong những minh họa tốt nhất đặt ra sự khác biệt giữa các chức năng an toàn lại và ren. Cụ thể là bạn đã sử dụng các thuật ngữ rõ ràng ngắn gọn và đã chọn một hàm ví dụ tuyệt vời để phân biệt giữa 4 danh mục. Vì vậy, cảm ơn! – ryyker

+0

Một chức năng là cả reentrant và thread-safe nếu nó không sử dụng bất kỳ var toàn cục/tĩnh nào. Chủ đề - an toàn: khi nhiều chủ đề chạy chức năng của bạn cùng một lúc, có cuộc đua nào không ?? Nếu bạn sử dụng var toàn cầu, hãy sử dụng khóa để bảo vệ nó. vì vậy nó an toàn chỉ. reentrant: nếu một tín hiệu xảy ra trong quá trình thực thi chức năng của bạn, và gọi chức năng của bạn trong tín hiệu một lần nữa, nó có an toàn không ??? trong trường hợp này, không có nhiều luồng.bạn sẽ không sử dụng bất kỳ var tĩnh/global để làm cho nó reentrant ... –

+0

Dường như với tôi thay exemple 3 không reentrant: nếu một handler tín hiệu, ngắt sau 't = * x', gọi' swap() ', sau đó 't' sẽ bị ghi đè, dẫn đến kết quả không mong muốn. – rom1v

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