Threadsafe vs re-entrant - PullRequest
       27

Threadsafe vs re-entrant

82 голосов
/ 13 мая 2009

Недавно я задал вопрос с названием "Безопасен ли поток malloc?" , а внутри я спросил: "Является ли malloc повторно входящим?"

У меня сложилось впечатление, что все возвращающиеся являются поточно-ориентированными.

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

Ответы [ 3 ]

66 голосов
/ 31 октября 2015

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

Статьи Википедии для Thread-Safety и 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;
}
55 голосов
/ 13 мая 2009

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

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

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

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

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

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

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

40 голосов
/ 13 мая 2009

Повторно входящие функции не полагаются на глобальные переменные, которые отображаются в заголовках библиотеки 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;
}

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

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

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

...