Реентерабельность
Компьютерная программа в целом или её отдельная процедура называется реентера́бельной (от англ. reentrant — повторно входимый), если она разработана таким образом, что одна и та же копия инструкций программы в памяти может быть совместно использована несколькими пользователями или процессами. При этом второй пользователь может вызвать реентерабельный код до того, как с ним завершит работу первый пользователь и это как минимум не должно привести к ошибке, а при корректной реализации не должно вызвать потери вычислений (то есть не должно появиться необходимости выполнять уже выполненные фрагменты кода).
Реентерабельность тесно связана с безопасностью функции в многопоточной среде (thread-safety), тем не менее, это разные понятия. Обеспечение реентерабельности является ключевым моментом при программировании многозадачных систем, в частности, операционных систем.
Для обеспечения реентерабельности необходимо выполнение нескольких условий:
- никакая часть вызываемого кода не должна модифицироваться;
- вызываемая процедура не должна сохранять информацию между вызовами;
- если процедура изменяет какие-либо данные, то они должны быть уникальными для каждого пользователя;
- процедура не должна возвращать указатели на объекты, общие для разных пользователей.
В общем случае, для обеспечения реентерабельности необходимо, чтобы вызывающий процесс или функция каждый раз передавал вызываемому процессу все необходимые данные. Таким образом, функция, которая зависит только от своих параметров, не использует глобальные и статические переменные и вызывает только реентерабельные функции, будет реентерабельной. Если функция использует глобальные или статические переменные, необходимо обеспечить, чтобы каждый пользователь хранил свою локальную копию этих переменных.
Пример
В следующем фрагменте кода функции f() и g() не являются реентерабельными.
int g_var = 1; int f() { g_var = g_var + 2; return g_var; } int g() { return f() + 2; }
Здесь f() зависит от глобальной переменной g_var, поэтому, если два процесса вызывают f() в одно и то же время, результат непредсказуем. Поэтому, f() не реентерабельна. Но и g() не реентерабельна, поскольку она использует нереентерабельную функцию f().
В следующем фрагменте кода функция accum() также не является реентерабельной.
int accum(int b) { static int a = 0; ++a; return (a+b); }
Здесь accum — функция, накапливающая значение а, за которое отвечает статическая переменная. Если accum будет вызвана разными процессами, то результат также будет непредсказуем. Как и в предыдущем примере a является общей для всех вызывающих её процессов.
Также потеря реентерабельности может встречаться тогда, когда в выражении используется больше одного раза одна и та же переменная.
#define SQR(x) ((x)*(x))
void func(void) { int x, y; x = SQR(y); }
В этом случае макрос SQR(x) будет работать некорректно, если при каждом обращении к аргументу он изменяется.
Ссылки
- Определение (англ.)
- Writing Reentrant and Thread-Safe Code (недоступная ссылка)