Недавно я задал вопрос с заголовком «Является ли malloc потокобезопасным?» , и внутри я спросил: «Маллок возвращается?»
У меня создалось впечатление, что все реентерабельные являются потокобезопасными.
Это предположение неверно?
источник
Недавно я задал вопрос с заголовком «Является ли malloc потокобезопасным?» , и внутри я спросил: «Маллок возвращается?»
У меня создалось впечатление, что все реентерабельные являются потокобезопасными.
Это предположение неверно?
Функции с повторным входом не полагаются на глобальные переменные, которые представлены в заголовках библиотеки C. Возьмите strtok () и strtok_r (), например, в C.
Некоторым функциям требуется место для хранения «незавершенной работы», реентерабельные функции позволяют указать этот указатель в собственном хранилище потока, а не в глобальном. Поскольку это хранилище является эксклюзивным для вызывающей функции, оно может быть прервано и повторно введено (повторный вход), и поскольку в большинстве случаев взаимное исключение сверх того, что реализует функция, не требуется для того, чтобы это работало, они часто считаются потокобезопасный . Однако это не гарантируется по определению.
errno, однако, это немного другой случай в системах POSIX (и имеет тенденцию быть странным в любом объяснении того, как все это работает) :)
Короче говоря, реентерабельность часто означает потокобезопасность (например, «используйте реентерабельную версию этой функции, если вы используете потоки»), но поточная безопасность не всегда означает реентерабельность (или наоборот). Когда вы смотрите на безопасность потоков, вам нужно думать о параллелизме . Если вам необходимо предоставить средства блокировки и взаимного исключения для использования функции, то эта функция по своей сути не является потокобезопасной.
Но не все функции нужно проверять. malloc()
не требует повторного входа, он не зависит от чего-либо, выходящего за пределы точки входа для любого данного потока (и сам по себе потокобезопасен).
Функции, возвращающие статически распределенные значения, не являются потокобезопасными без использования мьютекса, фьютекса или другого механизма атомарной блокировки. Тем не менее, им не нужно повторно входить, если их не собираются прерывать.
то есть:
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;
}
Итак, как вы можете видеть, использование нескольких потоков без какой-либо блокировки было бы катастрофой ... но это не имеет смысла повторно входить. Вы столкнетесь с этим, когда динамически выделяемая память является табу на какой-то встроенной платформе.
В чисто функциональном программировании реентерабельность часто не подразумевает потокобезопасность, это будет зависеть от поведения определенных или анонимных функций, переданных в точку входа функции, рекурсии и т. Д.
Лучший способ сделать «потокобезопасным» - безопасный для одновременного доступа , что лучше иллюстрирует необходимость.
TL; DR: функция может быть реентерабельной, поточно-ориентированной, и той, и другой.
Стоит прочитать статьи в Википедии о безопасности потоков и повторном входе . Вот несколько цитат:
Функция является поточно-ориентированной, если:
Функция является реентерабельной, если:
В качестве примеров возможного повторного входа в Википедию приводится пример функции, предназначенной для вызова системными прерываниями: предположим, что она уже выполняется, когда происходит другое прерывание. Но не думайте, что вы в безопасности только потому, что вы не кодируете системные прерывания: у вас могут быть проблемы с повторным входом в однопоточной программе, если вы используете обратные вызовы или рекурсивные функции.
Примеры
(Немного изменено из статей Википедии)
Пример 1: не потокобезопасный, не реентерабельный
/* 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; }
Пример 2: потокобезопасный, не реентерабельный
/* 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; }
Пример 3: не потокобезопасный, реентерабельный
/* 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; }
Пример 4: потокобезопасный, реентерабельный
/* 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; }
источник
t = *x
, вызываетswap()
, тоt
будет переопределен, что приведет к неожиданным результатам.swap(5, 6)
который прерывается aswap(1, 2)
. Послеt=*x
,s=t_original
иt=5
. Теперь, после перерыва,s=5
иt=1
. Однако перед вторымswap
возвратом он восстановит контекст, сделавt=s=5
. Теперь вернемся к первомуswap
сt=5 and s=t_original
и продолжим послеt=*x
. Итак, функция действительно повторяется. Помните, что каждый вызов получает свою собственную копию,s
размещенную в стеке.Это зависит от определения. Например, Qt использует следующее:
но они также предупреждают:
источник