Повторный вход кода против безопасности потоков - PullRequest
26 голосов
/ 09 декабря 2008

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

Код возврата и безопасности потока

Я не смог ясно понять объяснение. Помощь будет оценена.

Ответы [ 2 ]

21 голосов
/ 09 декабря 2008

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

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

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

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

8 голосов
/ 09 декабря 2008

Эта статья гласит:

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

Там также написано:

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

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

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

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

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

...