Расовые условия также могут возникать в традиционных однопоточных программах - Clarity - PullRequest
0 голосов
/ 24 мая 2018

За последние несколько месяцев я прочитал несколько книг по параллельному программированию, и я решил завершить его изучением темы posix.

Я читаю " Программирование PThreads - стандарт Posix для лучшего многопроцессорного справочника по скорлупе ".В главе 5 (Pthreads и Unix) автор рассказывает об обработке сигналов в многопоточных программах.В разделе « Функции библиотеки Threadsafe и системные вызовы » автор сделал заявление, которого я не видел в большинстве книг, которые я читал о параллельном программировании.Утверждение было следующим:

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

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

Мне также хотелось бы знать, как обработчики сигналов могут вызывать РАСОВОЕ СОСТОЯНИЕ В ОДНОПРОСТРАННЫХ ПРОГРАММАХ

Примечание: Я не студент информатики,я был бы очень признателен за упрощенные условия

Ответы [ 6 ]

0 голосов
/ 24 мая 2018

Я собираюсь дать более общий ответ, чем вы просили.И это мой собственный, личный, прагматичный ответ, который не обязательно соответствует любому официальному формальному определению термина «состояние гонки».

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

Но даже несмотря на то, что я не занимаюсь многопоточным программированием, я все еще сталкиваюсь с определенными классами того, что время от времени мне кажется похожим на условия гонки.Вот три, которые я стараюсь помнить:

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

  2. Общие ресурсы ОС, как правило, в файловой системе. Если ваша программа обращается (или изменяет) к файлу или каталогу в файловой системе, к которому также обращается или изменяет другой процесс, у вас есть большой потенциал для состояния гонки.(Это неудивительно, поскольку в смысле информатики несколько процессов являются несколькими потоками. Они могут иметь отдельные адресные пространства, что означает, что они не могут мешать друг другу таким образом, но, очевидно, файловая система является общейресурс, где они все еще могут мешать друг другу.)

  3. Невозвратные функции, такие как strtok.Если функция поддерживает внутреннее статическое состояние, вы не можете иметь второй вызов этой функции, если активен другой экземпляр.Это вовсе не «состояние гонки» в формальном смысле, но оно имеет много одинаковых симптомов, а также некоторые из тех же исправлений: не используйте статические данные;попробуйте написать свои функции так, чтобы они возвращались.

0 голосов
/ 24 мая 2018

Во-первых, важно понять, что такое состояние гонки.Определение, данное Wikipedia :

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

Важно отметить, что программа может вести себя как должным образом, так и ненадлежащим образом в зависимости от времени или порядка выполнения.


Мы можем довольно легко создать «фиктивные» условия гонки в однопоточномпрограммы под этим определением.

bool isnow(time_t then) {
    time_t now = time(0);
    return now == then;
}

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


Сделав шаг вперед, мы можем написать еще одну фиктивную программу.

bool printHello() {
    sleep(10);
    printf("Hello\n");
}

Ожидаемое поведение вышеуказанной программы - вывести "Hello" после ожидания 10 секунд.

Если мы отправим сигнал SIGINT через 11 секунд после вызова нашей функции, все будет работать так, как ожидается.Если мы посылаем сигнал SIGINT через 3 секунды после вызова нашей функции, программа ведет себя некорректно и не печатает "Hello".

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

0 голосов
/ 24 мая 2018

Автор книги, в которой вы нашли, кажется, определяет термин "состояние расы" необычным образом, или, возможно, он просто использовал неправильный термин.

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

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

0 голосов
/ 24 мая 2018

Я не думаю, что это можно назвать расой в классическом смысле.Условия гонки имеют несколько стохастический характер, в зависимости от политики планировщика и времени.

Автор, вероятно, говорит о ошибках , которые могут возникнуть при доступе к одному и тому же объекту / ресурсу из нескольких рекурсивных вызовов.Но это поведение является полностью детерминированным и управляемым.

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

0 голосов
/ 24 мая 2018

Хорошо, рассмотрим следующий код:

#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int num = 2;

void lock_and_call_again() {
    pthread_mutex_lock(&mutex);
    if(num > 0) {
        --num;
        lock_and_call_again();
    }
}


int main(int argc, char** argv) {
    lock_and_call_again();
}

(скомпилируйте с gcc -pthread thread-test.c, если вы сохраните код как thread-test.c)

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

Это в основном то, что подразумевается в приведенном вами абзаце, ИМХО:

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

Если функция вызывает себя, как lock_and_call вышеэто то, что называется рекурсивный вызов .

Как объясняет Джеймс Большой, сигнал может появиться в любое время, и если обработчик сигнала зарегистрирован с этим сигналом, он будет вызываться непредсказуемовремя, если никакие меры не предпринимаются, даже когда тот же обработчик уже выполняется - что приводит к некоему неявному рекурсивному выполнению обработчика сигнала.

Если этот обработчик получает какой-то видблокировка, вы попадаете в тупик, даже без функции, вызывающей себя явно.Рассмотрим следующую функцию:

pthread_mutex_t mutex;

void my_handler(int s) {

    pthread_mutex_lock(&mutex);
    sleep(10);
    pthread_mutex_unnlock(&mutex);

}

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

Относительно формулировки цитаты:

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

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

0 голосов
/ 24 мая 2018

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

Итак, предположим, что у вашей программы есть некоторый глобальный флаг, который обработчик сигнала устанавливает в ответчтобы ... я не знаю ... SIGINT.И ваша программа проверяет флаг перед каждым вызовом f (x).

if (! flag) {
    f(x);
}

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

...