Недавно я задал вопрос с заголовком «Является ли 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)
который прерывается a swap(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 использует следующее:
Поточно-безопасная * функция может вызываться одновременно из нескольких потоков, даже если при вызовах используются общие данные, поскольку все ссылки на общие данные сериализуются.
Возвратная функция также может вызываться одновременно из нескольких потоков, но только тогда , когда каждый вызов использует свои собственные данные.
Следовательно, потокобезопасная функция всегда является реентерабельной, но реентерабельная функция не всегда потокобезопасна.
В более широком смысле, класс называется реентерабельным, если его функции-члены могут быть безопасно вызваны из нескольких потоков, если каждый поток использует другой экземпляр класса. Класс является потокобезопасным, если его функции-члены можно безопасно вызывать из нескольких потоков, даже если все потоки используют один и тот же экземпляр класса.
но они также предупреждают:
Примечание. Терминология в области многопоточности не полностью стандартизирована. POSIX использует определения реентерабельности и потокобезопасности, которые несколько отличаются для его C API. При использовании других объектно-ориентированных библиотек классов C ++ с Qt убедитесь, что определения понятны.