Подтвердить что ты не робот

Реконструкция кода против безопасности потоков

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

Реентерабельный и потоковый код

Я не мог четко понять объяснение. Помощь была бы оценена.

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

В этой статье говорится:

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

В нем также говорится:

"Невозвратные функции небезопасны".

Я вижу, как это может вызвать путаницу. Они означают, что стандартные функции, задокументированные как не обязательные для повторного входа, также не должны быть потокобезопасными, что справедливо для библиотек POSIX iirc (и POSIX заявляет, что это также относится к библиотекам ANSI/ISO, ISO имеет нет концепции потоков и, следовательно, нет концепции безопасности потока). Другими словами, "если функция говорит, что она не реентерабельная, то она также говорит, что она небезопасна". Это не логическая необходимость, это просто соглашение.

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

take_global_lock();
int i = get_global_counter();
do_callback(i);
set_global_counter(i+1);
release_global_lock();

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

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

Здесь некоторый псевдокод, который "слабо повторен", но не потокобезопасен:

int i = get_global_counter();
do_callback(i);
set_global_counter(get_global_counter()+1);

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

Вот некоторый код, который, возможно, полностью повторен (кроме того, я считаю, что стандарт отличает реентерабельный и "не прерываемый по сигналам", и я не уверен, где это падает), но все же не является потокобезопасным

int i = get_global_counter();
do_callback(i);
disable_signals(); // and any other kind of interrupts on your system
set_global_counter(get_global_counter()+1);
restore_signal_state();

В однопоточном приложении это прекрасно, если предположить, что ОС поддерживает отключение всего, что нужно отключить. Это предотвращает повторное проникновение в критическую точку. В зависимости от того, как отключены сигналы, может быть безопасно звонить из обработчика сигнала, хотя в этом конкретном примере все еще проблема параметра, переданного обратному вызову, одинакова для отдельных вызовов. Тем не менее, он все равно ошибается многопоточным.

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

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