Có ít nhất ba khái niệm ở đây, tất cả đều được viết bằng ngôn ngữ thông tục, có thể là lý do bạn nhầm lẫn.
- thread-safe
- phần quan trọng
- góc lõm
Chịu cách đơn giản nhất đầu tiên: Cả malloc
và printf
là thread-safe. Họ đã được đảm bảo an toàn thread trong tiêu chuẩn C kể từ năm 2011, trong POSIX từ năm 2001, và trong thực tế từ lâu trước đó. Điều này có nghĩa là các chương trình sau đây được đảm bảo không để sụp đổ hoặc thể hiện hành vi xấu:
#include <pthread.h>
#include <stdio.h>
void *printme(void *msg) {
while (1)
printf("%s\r", (char*)msg);
}
int main() {
pthread_t thr;
pthread_create(&thr, NULL, printme, "hello");
pthread_create(&thr, NULL, printme, "goodbye");
pthread_join(thr, NULL);
}
Một ví dụ về chức năng mà là không thread-safe là strtok
. Nếu bạn gọi strtok
từ hai luồng khác nhau cùng lúc, kết quả là hành vi không xác định - bởi vì strtok
sử dụng bộ đệm tĩnh để theo dõi trạng thái của nó. glibc thêm strtok_r
để khắc phục sự cố này và C11 đã thêm cùng một điều (nhưng tùy chọn và dưới tên khác, vì Không được phát minh tại đây) là strtok_s
.
Được rồi, nhưng không sử dụng nguồn lực toàn cầu để xây dựng đầu ra của nó? Trong thực tế, nó sẽ là gì ngay cả có nghĩa là để in tới stdout từ hai luồng cùng một lúc? Điều đó đưa chúng ta đến chủ đề tiếp theo. Rõ ràng là printf
sẽ là critical section trong bất kỳ chương trình nào sử dụng nó. Chỉ có một luồng thực thi được phép ở bên trong phần quan trọng cùng một lúc.
Ít nhất trong các hệ thống POSIX-compliant, điều này được thực hiện bằng cách printf
bắt đầu với một cuộc gọi đến flockfile(stdout)
và kết thúc bằng một cuộc gọi đến funlockfile(stdout)
, đó là cơ bản giống như tham gia một mutex toàn cầu liên quan đến thiết bị xuất chuẩn.
Tuy nhiên, mỗi khác biệt FILE
trong chương trình được phép có mutex riêng. Điều này có nghĩa là một sợi có thể gọi fprintf(f1,...)
cùng một lúc mà luồng thứ hai đang ở giữa cuộc gọi đến fprintf(f2,...)
. Không có điều kiện chủng tộc ở đây. (Cho dù libc của bạn thực sự chạy hai cuộc gọi song song là một vấn đề QoI. Tôi không thực sự biết glibc làm gì.)
Tương tự, malloc
không phải là một phần quan trọng trong bất kỳ hệ thống hiện đại nào, bởi vì hệ thống hiện đại smart enough to keep one pool of memory for each thread in the system, thay vì có tất cả các chủ đề N chiến đấu trên một hồ bơi duy nhất. (Cuộc gọi sbrk
hệ thống sẽ vẫn có thể là một phần quan trọng, nhưng malloc
dành rất ít thời gian của mình trong sbrk
. Hoặc mmap
, hoặc bất cứ điều gì những đứa trẻ mát mẻ đang sử dụng những ngày này.)
Được rồi, vì vậy những gì hiện re-entrancy thực nghĩa là? Về cơ bản, nó có nghĩa là chức năng có thể được gọi một cách an toàn một cách đệ quy - lời gọi hiện tại là "tạm dừng" trong khi lệnh gọi thứ hai chạy, và sau đó lời gọi đầu tiên vẫn có thể "bắt đầu từ nơi nó dừng lại". (Về mặt kỹ thuật, có thể là không phải do cuộc gọi đệ quy: lời gọi đầu tiên có thể nằm trong Chủ đề A, bị gián đoạn ở giữa bởi Chủ đề B, thực hiện lệnh gọi thứ 2. Nhưng kịch bản đó chỉ là trường hợp đặc biệt là -safety, vì vậy chúng ta có thể quên nó ở đoạn này.)
Cả printf
cũng không malloc
có thể có thể được gọi đệ quy bằng một chủ đề duy nhất, bởi vì họ là những chức năng lá (họ không gọi mình cũng không gọi ra cho bất kỳ mã do người dùng kiểm soát nào có thể thực hiện cuộc gọi đệ quy). Và, như chúng ta đã thấy ở trên, họ đã được an toàn thread chống lại các cuộc gọi lại đa luồng * đa luồng từ năm 2001 (bằng cách sử dụng khóa).
Vì vậy, bất cứ ai đã nói với bạn rằng printf
và malloc
là không phải là reentrant đã sai; những gì họ có nghĩa là để nói có lẽ là cả hai người trong số họ có khả năng được phần quan trọng trong chương trình của bạn - tắc nghẽn, nơi chỉ có một sợi có thể nhận được thông qua tại một thời điểm.
lưu ý pedantic: glibc không cung cấp một phần mở rộng mà printf
có thể được thực hiện để gọi mã người dùng tùy ý, bao gồm tái tự xưng. Điều này là hoàn toàn an toàn trong tất cả các hoán vị của nó - ít nhất là theo như thread-an toàn là có liên quan. (Rõ ràng là nó mở ra cánh cửa hoàn toàn có lỗ hổng điên.) Có hai biến thể: register_printf_function
(được ghi lại và hợp lý lành mạnh nhưng chính thức "không được chấp nhận") và register_printf_specifier
(là gần như giống hệt nhau tham số không có giấy tờ và total lack of user-facing documentation). Tôi sẽ không đề nghị một trong số họ, và đề cập đến họ ở đây chỉ là một thú vị sang một bên.
#include <stdio.h>
#include <printf.h> // glibc extension
int widget(FILE *fp, const struct printf_info *info, const void *const *args) {
static int count = 5;
int w = *((const int *) args[0]);
printf("boo!"); // direct recursive call
return fprintf(fp, --count ? "<%W>" : "<%d>", w); // indirect recursive call
}
int widget_arginfo(const struct printf_info *info, size_t n, int *argtypes) {
argtypes[0] = PA_INT;
return 1;
}
int main() {
register_printf_function('W', widget, widget_arginfo);
printf("|%W|\n", 42);
}
@ ripunjay-tripathi: printf, nếu đang in tài nguyên chung, ví dụ: stdio. malloc bởi vì nó dựa vào ổ khóa. Hãy nhớ rằng có một sự khác biệt giữa Reentrant và an toàn thread. nơi malloc an toàn chỉ. nhìn vào bài đăng này. http://stackoverflow.com/questions/855763/malloc-thread-safe. – yadab
Bạn đã tìm thấy "triển khai chuẩn" ở đâu? Chức năng là reentrant nếu các nhà phát triển nói rằng họ đang có. nhà phát triển glibc không nói malloc hoặc printf là reentrant: vì vậy họ không. – pmg
@pmg, các từ về "triển khai chính tắc" đã được tôi thêm vào. Đây là ý tôi. Rõ ràng là reentrancy là một thuộc tính của việc thực thi, không phải của một giao diện. Tuy nhiên, ví dụ, POSIX không liệt kê 'malloc' và' printf' là hàm reentrant, và đây là lý do. Trong quesiton này, OP muốn biết lý do là gì. –