Статические переменные и потоки (C)
Я знаю, что объявление статической переменной внутри функции в C означает, что эта переменная сохраняет свое состояние между вызовами функций. В контексте потоков это приведет к тому, что переменная сохранит свое состояние в нескольких потоках или будет иметь отдельное состояние между каждым потоком?
Вот прошлый бумажный экзаменационный вопрос, на который я пытаюсь ответить:
Следующая функция C предназначена для использования для выделения уникальных идентификаторов (UID) своим вызывающим:
get_uid() { static int i = 0; return i++; }
Объясните, как get_uid() может работать некорректно в среде, в которой он вызывается несколькими потоками. Используя конкретный пример сценария, подробно опишите, почему и как может происходить такое неправильное поведение.
В настоящий момент я предполагаю, что каждый поток имеет отдельное состояние для переменной, но я не уверен, правильно ли это или ответ больше связан с отсутствием взаимного исключения. Если это так, то как семафоры могут быть реализованы в этом примере?
6 ответов
Ваше предположение (темы имеют свою собственную копию) неверно. Основная проблема с кодом - это когда несколько потоков вызывают эту функцию get_uid()
есть возможное условие гонки относительно того, какие потоки увеличиваются i
и получает идентификатор, который не может быть уникальным.
Все потоки процесса имеют одинаковое адресное пространство. поскольку i
является статической переменной, она имеет фиксированный адрес. Его "состояние" - это просто содержимое памяти по этому адресу, которое совместно используется всеми потоками.
Постфикс ++
Оператор увеличивает свой аргумент и возвращает значение аргумента перед приращением. Порядок, в котором они выполняются, не определен. Одна из возможных реализаций
copy i to R1
copy R1 to R2
increment R2
copy R2 to i
return R1
Если запущено более одного потока, они могут выполнять эти инструкции одновременно или с перемежением. Разработайте для себя последовательности, в которых можно получить различные результаты. (Обратите внимание, что каждый поток имеет свое собственное состояние регистра, даже для потоков, работающих на одном и том же ЦП, поскольку регистры сохраняются и восстанавливаются при переключении потоков.)
Подобная ситуация, когда есть разные результаты в зависимости от недетерминированного порядка операций в разных потоках, называется условием гонки, потому что среди разных потоков существует "гонка" относительно того, какой из них выполняет какую операцию первым.
Нет, если вы хотите переменную, значение которой зависит от потока, в котором она используется, вы должны взглянуть на Thread Local Storage.
Статическая переменная, вы можете себе представить, что это действительно глобальная переменная. Это действительно так же. Таким образом, он является общим для всей системы, которая знает его адрес.
РЕДАКТИРОВАТЬ: также в качестве комментария напоминает, что если вы сохраните эту реализацию в качестве статической переменной, условия гонки могут сделать это значение i
увеличивается одновременно несколькими потоками, что означает, что вы не имеете никакого представления о значении, которое будет возвращено вызовами функции. В таких случаях вы должны защищать доступ с помощью так называемых объектов синхронизации, таких как мьютексы или критические секции.
Поскольку это выглядит как домашнее задание, я отвечу только на часть этого, и каждый поток будет использовать одну и ту же копию i
, IOW, потоки не получают свои собственные копии. Я оставлю вам немного взаимного исключения.
Если вы используете gcc, вы можете использовать встроенные атомарные функции. Я не уверен, что доступно для других компиляторов.
int get_uid()
{
static int i = 0;
return __atomic_fetch_add(&i, 1, __ATOMIC_SEQ_CST);
}
Это гарантирует, что переменная не может быть обработана более чем одним потоком одновременно.
Каждый поток будет использовать одну и ту же статическую переменную, которая, скорее всего, является глобальной переменной. Сценарий, в котором некоторые потоки могут иметь неправильное значение, - это состояние гонки (приращение не выполняется за одно выполнение, а выполняется в 3 инструкциях по сборке: загрузка, приращение, сохранение). Прочитайте здесь, и диаграмма в ссылке объясняет это хорошо.