23:53
Свиноматка
Хуже всего, если баг в программе то проявляется, то нет, по непонятному расписанию.
Практика показывает, что в таких случаях баг чаще всего вызван многопоточностью. То есть, когда несколько частей программы выполняются одновременно. Многопоточность -- действительно сложная тема со множеством подводных камней.
Если вы обнаружили подобный баг, то следует проверить следующее:
1) Что доступ к внешним устройствам есть только у одного потока. Если есть сомнения, следует сделать специальную функцию доступа к нему, которая будет работать через взаимоисключение (т.е. мьютекс, критическая секция и пр.). Весь доступ к устройству должен осуществляться через неё.
2) Что доступ к внутренним ресурсам (особенно, к памяти) размером больше ширины шины данных тоже сделан через взаимоисключения (см. примечание ниже).
3) И, наконец, что вы не применяете многопоточно библиотеки, которые для этого не предназначены. Если предназначены, обычно стоит пометка в документации -- thread-safe. Одна из таких библиотек, которая не предназначена -- VCL (C++ Builder, Delphi). Когда-то я не знал, что она не поддерживает многопоточность и менял, к примеру, надписи на кнопках из разных потоков. Это приводило к совершенно невообразимым глюкам.
Внимание. Примечание к п.2. На некоторых процессорах доступ к невыровненным данным выполняется в несколько тактов. Выровненными считаются данные, расположенные по адресам, равным n*ширина шины данных, где n -- целое неотрицательное число. Т.е. если ваша переменная размером 4 байта расположена по адресу 13, то её считывание на 32-битном процессоре может происходит следующим образом: процессор считывает байты 12-15, сохраняет во временный регистр, потом 16-19, сохраняет в другой регистр, сдвигает 12-15 влево на 8 бит, записывает старшие 24 бита в старшие 24 бита целевого регистра, потом сдгвигает 16-19 вправо на 24 бита, записывает младшие 8 бит в младшие 8 бит целевого регистра. Данный алгоритм является примером, фактическая реализация может быть иной. За это время потоки могли переключиться уже несколько раз. Будьте осторожны. Если же адрес переменной изначально был 12, то всё проходит в 1 такт.
Практика показывает, что в таких случаях баг чаще всего вызван многопоточностью. То есть, когда несколько частей программы выполняются одновременно. Многопоточность -- действительно сложная тема со множеством подводных камней.
Если вы обнаружили подобный баг, то следует проверить следующее:
1) Что доступ к внешним устройствам есть только у одного потока. Если есть сомнения, следует сделать специальную функцию доступа к нему, которая будет работать через взаимоисключение (т.е. мьютекс, критическая секция и пр.). Весь доступ к устройству должен осуществляться через неё.
2) Что доступ к внутренним ресурсам (особенно, к памяти) размером больше ширины шины данных тоже сделан через взаимоисключения (см. примечание ниже).
3) И, наконец, что вы не применяете многопоточно библиотеки, которые для этого не предназначены. Если предназначены, обычно стоит пометка в документации -- thread-safe. Одна из таких библиотек, которая не предназначена -- VCL (C++ Builder, Delphi). Когда-то я не знал, что она не поддерживает многопоточность и менял, к примеру, надписи на кнопках из разных потоков. Это приводило к совершенно невообразимым глюкам.
Внимание. Примечание к п.2. На некоторых процессорах доступ к невыровненным данным выполняется в несколько тактов. Выровненными считаются данные, расположенные по адресам, равным n*ширина шины данных, где n -- целое неотрицательное число. Т.е. если ваша переменная размером 4 байта расположена по адресу 13, то её считывание на 32-битном процессоре может происходит следующим образом: процессор считывает байты 12-15, сохраняет во временный регистр, потом 16-19, сохраняет в другой регистр, сдвигает 12-15 влево на 8 бит, записывает старшие 24 бита в старшие 24 бита целевого регистра, потом сдгвигает 16-19 вправо на 24 бита, записывает младшие 8 бит в младшие 8 бит целевого регистра. Данный алгоритм является примером, фактическая реализация может быть иной. За это время потоки могли переключиться уже несколько раз. Будьте осторожны. Если же адрес переменной изначально был 12, то всё проходит в 1 такт.
23.06.2015 в 09:52
24.06.2015 в 23:59
размером больше ширины шины данных
И меньше тоже. A++ из двух потоков рано или поздно кончится плохо. Без блокировок можно использовать:
1. Переменные, локальные для потока.
2. Атомарные операции по отдельности (чтение, присвоение, проверка, Interlocked***), но не их комбинации.
Существует целая отрасль, в которой свои талмуды и профессионалы - lockless synchronization. Типичный приём - lockless однократная инициализация:
Т.е. создать, попробовать заменить NULL на него, но если там был не NULL, то кто-то успел раньше, уничтожить лишний object.
А ещё есть гейзенбаги, это которые исчезают, когда на них смотришь отладчиком (или логом)
25.06.2015 в 18:16
Что а плюс плюс закончится плохо -- да. Про это надо подправить. Спасибо.
01.07.2015 в 22:48
01.07.2015 в 23:50
02.07.2015 в 09:59
Ну, для гарантии (не 100%, но гарантия 100% - всегда ЛожьПП) получения и отправки сообщений с внешних устройств - в принципе, что ещё делать остаётся, управляющие сигналы а-ля мьютексы и проч. не всегда можно (или нужно) вводить в протокол. А с контроллерами всё действительно можно "подогнать" под эм... кадр.
Так что... что сказать-то... В общем, уверен, всё правильно делаете.
02.07.2015 в 13:13
03.07.2015 в 00:23