Câu hỏi Threadsafe vs người dự thi lại


Gần đây, tôi hỏi một câu hỏi, với tựa đề là "Chủ đề malloc có an toàn không?", và bên trong đó tôi hỏi, "Có phải người dự thi lại không?"

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

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


76
2018-05-13 08:43


gốc




Các câu trả lời:


Các hàm Re-entrant không phụ thuộc vào các biến toàn cầu được tiếp xúc trong các tiêu đề thư viện C .. lấy strtok () vs strtok_r () ví dụ trong C.

Một số chức năng cần 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 chỉ dành riêng cho chức năng gọi, nó có thể bị gián đoạn và đã nhập lại (Re-entrant) và vì trong hầu hết các trường hợp loại trừ lẫn nhau ngoài những gì các chức năng thực hiện là không cần thiết để làm việc này, chúng thường được coi 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 các hệ thống POSIX (và có xu hướng được các oddball trong bất kỳ lời giải thích về cách thức này tất cả các công trình) :)

Tóm lại, reentrant thường xuyên 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à tham gia lại (hoặc ngược lại). Khi bạn đang 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.

Tuy nhiên, 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ề các giá trị được cấp phát tĩnh là không phải 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.

I E.:

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 có thể thấy, có nhiều chủ đề sử dụng mà 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 được tái tham gia. 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 thuần túy chức năng, thường xuyên reentrant không ngụ ý chủ đề an toàn, nó sẽ phụ thuộc vào hành vi của các hàm được xác định hoặc ẩn danh được chuyển đến điểm nhập hàm, đệ quy, v.v.

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.


38
2018-05-13 08:55



Reentrant không ngụ ý thread-safe. Các hàm thuần túy ngụ ý sự an toàn của luồng. - Julio Guerra
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 có thể tìm thấy một ví dụ về hàm reentrant không phải thread-safe? - Riccardo
@ 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 Ngược lại: "Do đó, một chức năng an toàn chủ đề luôn luôn là reentrant, nhưng một chức năng reentrant không phải là luôn luôn thread-an toàn." - 4pie0
và wikipedia nói Một cái gì đó reentrant khác có thể đạt được thread-an toàn, [1] nhưng được reentrant một mình có thể không đủ để được thread-an toàn trong mọi tình huống Ngược lại, mã an toàn thread không nhất thiết phải là reentrant (...) " - 4pie0
@Riccardo: Các chức năng được đồng bộ hóa thông qua các biến dễ bay hơi nhưng không phải là các rào cản bộ nhớ đầy đủ để sử dụng với các trình xử lý tín hiệu / ngắt thường là người tham gia lại nhưng lại an toàn. - doynax


Nó phụ thuộc vào định nghĩa. Ví dụ Qt sử dụng những điều sau đây:

  • Chức năng * an toàn thread có thể được gọi đồng thời từ nhiều luồng, ngay cả khi các lời gọi sử dụng dữ liệu được chia sẻ, bởi vì tất cả các tham chiếu đến dữ liệu được chia sẻ đều được tuần tự hóa.

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

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

Bằng cách mở rộng, một lớp được cho là reentrant 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, miễn là mỗi luồng sử dụng một cá thể khác của lớp đó. Lớp học là ren an toàn 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 cá thể của lớp đó.

nhưng họ cũng thận trọng:

Chú thích: Thuật ngữ trong miền đa luồng không được chuẩn hóa hoàn toàn. 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.


54
2018-05-13 09:08



Định nghĩa về reentrant này quá mạnh. - qweruiop
Downvote. Một hàm an toàn thread KHÔNG phải lúc nào cũng reentrant. - SandBag_1996
Một hàm 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 hàm của bạn, và gọi hàm 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 nào để làm cho nó reentrant, hoặc giống như trong ví dụ 3. - keniee van


TL; DR: Một hàm có thể được reentrant, thread-safe, cả hai hoặc không.

Các bài viết trên Wikipedia cho chủ đề an toàn và reentrancy cũng đáng đọc. Dưới đây là một vài trích dẫn:

Hàm là ren an toàn nếu:

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

Hàm là reentrant nếu:

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

Như các ví dụ về sự tái phát triển có thể, Wikipedia đưa ra ví dụ về một hàm được thiết kế để được gọi bởi các ngắt hệ thống: giả sử nó đã chạy khi một 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 luồng. Nó là một khái niệm từ thời điểm   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 trên Wikipedia)

Ví dụ 1: không an toàn thread, 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 an toàn thread, 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;
}

53
2017-10-30 22:34



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à chủ đề. 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
Một hàm 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 hàm của bạn, và gọi hàm 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 nào để làm cho nó reentrant ... - keniee van
Dường như với tôi thay exemple 3 không phải là reentrant: nếu một bộ xử lý tín hiệu, ngắt sau t = *x, cuộc gọi swap(), sau đó t sẽ bị ghi đè, dẫn đến kết quả không mong muốn. - rom1v