Эта статья гласит:
"функция может быть либо реентерабельной, поточно-ориентированной, либо обе, либо ни одной."
Там также написано:
«Невозвратные функции небезопасны».
Я вижу, как это может привести к путанице. Они означают, что стандартные функции, задокументированные как не требующие повторного входа, также не обязательно должны быть поточно-ориентированными, что справедливо для библиотек 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();
В однопоточном приложении это нормально, если предположить, что ОС поддерживает отключение всего, что необходимо отключить. Это предотвращает повторный вход в критическую точку. В зависимости от того, как сигналы отключены, может быть безопасно вызывать из обработчика сигнала, хотя в этом конкретном примере все еще остается проблема, связанная с тем, что параметр, передаваемый в обратный вызов, является одинаковым для отдельных вызовов. Тем не менее, он все равно может пойти не так, как многопоточный.
На практике не-потокобезопасность часто подразумевает отсутствие повторного входа, поскольку (неформально) все, что может пойти не так из-за прерывания потока планировщиком и функции, вызываемой снова из другого потока, также может выполняться неправильно, если поток прерывается сигналом, и функция вызывается снова из обработчика сигнала. Но тогда «исправление» для предотвращения сигналов (их отключение) отличается от «исправления» для предотвращения параллелизма (обычно блокировки). Это в лучшем случае эмпирическое правило.
Обратите внимание, что здесь я подразумевал глобальные переменные, но точно такие же соображения применимы, если функция принимает в качестве параметра указатель на счетчик и блокировку. Просто различные случаи будут поточно-небезопасными или неповторяющимися при вызове с одним и тем же параметром, а не при вызове вообще.