#c #multithreading #visual-c #boolean #critical-section
Вопрос:
Я работаю над классом очереди с использованием потоков в C (Windows 10 Visual Studio).
В моем конструкторе я начинаю с установки логического значения ошибки в значение false. Сразу после этого я пытаюсь инициализировать критическую секцию. Если есть проблема, bool имеет значение true, в противном случае он остается ложным. Я проверяю это значение в основной функции (не показано) и завершаю программу, если критическая секунда не инициализирована (значение bool равно false).
Код конструктора:
Queue() {
// ERROR_CRIT is bool: false = no error in initialising crit section, true = there is an error
ERROR_CRIT = false;
// check init
if (!InitializeCriticalSectionAndSpinCount(amp;CritSec, 0x00000400)) {
ERROR_INIT_CRIT_SEC = true;
}
totalCustomers = 0;
currentReadIndex = 0;
currentWriteIndex = 0;
customerNum = 0;
};
Мой вопрос в следующем: какие типы логических значений я должен установить по умолчанию в значение true или false ? Я много раз думал об этом при написании других программ, и я не уверен, когда следует устанавливать значение по умолчанию для bool, если это не очевидно. Иногда кажется справедливым начать с любого значения. Я чувствую, что было бы странно, если бы код ошибки начинался как true, но его запуск как false также может быть странным.
В реальном мире или в компании я бы удалил строку 3 и добавил оператор else в if с else {ERROR_CRIT = false;}
помощью ? Улучшит ли это читабельность ? Это, вероятно, предпочтение, но что часто встречается в программировании реального мира ?
Заранее благодарю вас 😀
Комментарии:
1.
ERROR_INIT_CRIT_SEC = !InitializeCriticalSectionAndSpinCount(amp;CritSec, 0x00000400);
?2. Я бы установил значение по умолчанию для bool, если предполагаемое значение переменной таково, что во время ее инициализации вы можете определить, какое значение она должна иметь. То же самое с любой переменной, на самом деле, а не только с bool. В этом случае ответ на вопрос «произошла ли ошибка при инициализации критического раздела?» в момент
ERROR_CRIT
инициализации это «нет», поэтомуERROR_CRIT
по умолчаниюfalse
мне кажется разумным.3. Замените все
bool
s, их настройку и тестирование на исключение. Почему —Queue
объект находится не в хорошем состоянии, и это должно быть исключительной ошибкой.4. Общее эмпирическое правило: если вы не можете успешно построить, создайте исключение и внесите залог. Конструктор возвращает допустимый объект (исключая двухфазную конструкцию), и если он не может этого сделать, он должен заставить программу решить проблему или умереть..
5. Когда возникает исключение, вы не получаете объект. Вы не можете вызывать какие-либо методы тестирования, потому что у вас нет объекта. У вас либо есть отдельный путь кода, который позволяет обходиться без объекта, вы повторяете, пока не создадите объект успешно, либо вы зовете на помощь (сообщаете об ошибке) и прерываете программу. Часто вы будете использовать комбинацию 2 и 3: сделайте несколько повторных попыток, а затем сообщите и умрите.
Ответ №1:
Гораздо лучший подход состоит в том, чтобы вообще запретить существование очереди, если критический раздел не может быть создан. Это устанавливает более жесткий инвариант класса для Queue
класса, что упрощает его реализацию.
Это делается путем создания исключения:
Queue() {
// check init
if (!InitializeCriticalSectionAndSpinCount(amp;CritSec, 0x00000400)) {
throw std::runtime_error("failed to create the critical section");
}
totalCustomers = 0;
currentReadIndex = 0;
currentWriteIndex = 0;
customerNum = 0;
};
Таким образом, вам никогда не нужно проверять наличие ERROR_CRIT
, так как существующей очереди достаточно информации, чтобы гарантировать правильную инициализацию критического раздела.
Комментарии:
1. Ах, значит, исключение будет встроено в конструктор, а не отдельной функцией-членом ? Подвох здесь был бы в основном, и я мог бы решить, что тогда произойдет с программой ? Бросая, остальная часть конструктора будет проигнорирована правильно ? Только оператор if будет завершен, если произойдет бросок ?
2. @HusamChekfa Точно, создание исключения внутри конструктора приведет к прерыванию процесса создания объекта, а также любого кода, следующего за созданием объекта, до
try
тех пор, пока не будет выведен соответствующий блок. Как с этим справиться вmain()
зависимости от того, что вы хотите сделать, когда произойдет сбой. Если вы собирались выйти из программы, то вы можете просто оставить исключение без обработки, и это будет эквивалентно. В противном случае вам нужно перехватить/обработать исключение в соответствии с желаемым поведением.3. Спасибо. Возможность полностью избежать создания bool и использовать вместо него метод броска идеально подходит
Ответ №2:
Хотя я в целом согласен с принятым ответом (и поддержал его), я хочу добавить, что устоявшийся подход в современной ОС заключается в том, что примитивы синхронизации в процессе работы не отказывают по причинам нехватки ресурсов или чего-то в этом роде.
Они могут выйти из строя только при неправильном использовании, если существует диагностика таких сбоев.
Это подтверждается InitializeCriticalSectionAndSpinCount
документацией:
Возвращаемое значение
Эта функция всегда выполняется успешно и возвращает ненулевое значение.
Windows Server 2003 и Windows XP: бла-бла-бла
Причина заключается в том, что при сбое примитивов синхронизации восстановление очень сложно, но можно иметь примитивы синхронизации, которые никогда не отказывают. Таким образом, в современной операционной системе код обработки сбоев никогда не может выполняться, и для того, чтобы операционная система все еще не могла восстановить программу.
C std::mutex
и другие мьютексы C согласуются с этим и никогда не выбрасываются из-за нехватки памяти или ресурсов (в частности std::mutex
, даже constexpr
могут быть сконструированы, хотя Visual C нарушает это). Новые примитивы C 20 также не создают исключений.
Так что вы можете просто написать:
Queue() noexcept {
InitializeCriticalSectionAndSpinCount(amp;CritSec, 0x00000400);
totalCustomers = 0;
currentReadIndex = 0;
currentWriteIndex = 0;
customerNum = 0;
};
или, если вы параноик:
Queue() noexcept {
// check init
if (!InitializeCriticalSectionAndSpinCount(amp;CritSec, 0x00000400)) {
__fastfail(1); // something terrible happened, cannot recover
}
totalCustomers = 0;
currentReadIndex = 0;
currentWriteIndex = 0;
customerNum = 0;
};