Threadsafe vs re-entrant

В последнее время я задал вопрос с заголовком "Безопасен ли поток malloc?" и внутри этого вопроса я спросил: "Является ли malloc re-entrant?"

У меня создалось впечатление, что все повторные участники потокобезопасны.

Это предположение неверно?

Ответ 1

Функции повторного входа не зависят от глобальных переменных, которые отображаются в заголовках библиотеки C. Возьмите strtok() vs strtok_r(), например, в C.

Некоторым функциям необходимо место для хранения "незавершенной работы", функции повторного входа позволяют указывать этот указатель внутри собственного хранилища потоков, а не в глобальном. Поскольку это хранилище является исключительным для вызывающей функции, оно может быть прервано и повторно введено (повторное участие), и поскольку в большинстве случаев взаимное исключение, превышающее то, что выполняет функция, не требуется для этого, они часто считаются безопасный поток. Однако это не гарантирует по определению.

errno, однако, немного отличается от POSIX-систем (и имеет тенденцию быть странным в любом объяснении того, как все это работает):)

Короче говоря, реентератор часто означает потокобезопасность (например, "использовать реентерабельную версию этой функции, если вы используете потоки" ), но потокобезопасность не всегда означает повторный вход (или наоборот). Когда вы смотрите на безопасность потоков, concurrency - это то, о чем вам нужно думать. Если вам необходимо обеспечить блокировку и взаимное исключение для использования функции, то функция не является по своей сути потокобезопасной.

Но не все функции должны быть проверены для них. malloc() не нужно быть реентерабельным, он не зависит от чего-либо вне области ввода точки для любого данного потока (и сам по себе является потокобезопасным).

Функции, возвращающие статически распределенные значения, не являются потокобезопасными без использования мьютекса, futex или другого механизма блокировки атома. Тем не менее, им не нужно быть реентерабельными, если они не будут прерваны.

то есть:.

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;
}

Итак, как вы можете видеть, использование нескольких потоков без какой-либо блокировки было бы катастрофой.. но у него нет никакой цели быть повторным. Вы столкнетесь с этим, когда динамически распределенная память является табу на некоторой встроенной платформе.

В чисто функциональном программировании реентератор часто не подразумевает потокобезопасность, он будет зависеть от поведения определенных или анонимных функций, переданных в точку входа функции, рекурсию и т.д.

Лучший способ поставить "потокобезопасный" безопасен для одновременного доступа, что лучше иллюстрирует необходимость.

Ответ 2

TL; DR: функция может быть реентерабельной, потокобезопасной, обе или ни одна.

Статьи в Википедии для безопасность потоков и reentrancy хорошо стоит прочитать. Вот несколько цитат:

Функция потокобезопасная, если:

он управляет только совместными структурами данных в способ, гарантирующий безопасное выполнение несколькими потоков в то же время.

Функция реентерабельная, если:

он может быть прерван в любой момент во время его выполнения и затем безопасно снова вызван ( "повторно введен" ) до его выполнение предыдущих вызовов завершено.

В качестве примеров возможного повторного входа в Википедию приведен пример функции, которая вызывается системными прерываниями: предположим, что она уже работает, когда происходит другое прерывание. Но не думайте, что вы в безопасности, потому что вы не кодируете системные прерывания: у вас могут возникать проблемы с повторным подключением в однопоточной программе, если вы используете обратные вызовы или рекурсивные функции.

Ключом к недопущению путаницы является то, что реентерант относится к выполняется только один поток. Это концепция с того времени, когда не существовало многозадачных операционных систем.

<сильные > Примеры

(Немного изменен из статей в Википедии)

Пример 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;
}

Ответ 3

Это зависит от определения. Например Qt использует следующее:

  • Функция потокобезопасности * может быть вызвана одновременно из нескольких потоков, даже если вызовы используют общие данные, поскольку все ссылки на разделяемые данные сериализуются.

  • Повторная функция также может быть вызвана одновременно из нескольких потоков, но только если каждый вызов использует свои собственные данные.

Следовательно, поточно-безопасная функция всегда реентерабельная, но повторная функция не всегда безопасна для потоков.

В дополнение, класс называется реентерабельным, если его функции-члены можно безопасно называть из нескольких потоков, если каждый поток использует другой экземпляр класса. Класс является потокобезопасным, если его функции-члены можно безопасно называть из нескольких потоков, даже если все потоки используют один и тот же экземпляр класса.

но они также предупреждают:

Примечание. Терминология в многопоточном домене не полностью стандартизирована. POSIX использует определения реентерабельных и потокобезопасных, которые несколько отличаются для своих API-интерфейсов C. При использовании других объектно-ориентированных библиотек классов С++ с Qt, убедитесь, что определения понятны.