LINUX.ORG.RU

Отладка ошибки многопоточности

 ,


0

4

Столкнулся с некоторой проблемой в своём проекте. Имеются сервисные функции засыпания-пробуждения нитей:

void Thread::resume() {
	pthread_mutex_lock(&m_sleepMutex);
	m_disableSleep++;
	pthread_mutex_unlock(&m_sleepMutex);
	pthread_cond_broadcast(&m_sleepCond);
}
		
static void Thread::sleep() {
	Thread* thread = current();
	pthread_mutex_lock(&(thread->m_sleepMutex));
	while (thread->m_disableSleep <= 0) {
		pthread_cond_wait(&(thread->m_sleepCond), &(thread->m_sleepMutex));
	}
	thread->m_disableSleep--;
	pthread_mutex_unlock(&(thread->m_sleepMutex));
}

А также некоторая логика:

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

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

Я также сделал проверку - передавал в resume булева-параметр (значение по умолчанию сделал false), который сохранял в классе потока. В коде, который сбрасывает флаг, передавал инвертированное текущее значение флага после сброса (то есть true). А в потоке 1 считывал поле класса потока в локальную переменную (а затем присваиваю полю класса false), а затем в новую локальную переменную - текущее состояние флага. В отладчике ставлю точку остановка на команду сразу после создания этих локальных переменных и получаю, что оба флага равны true, хотя такого быть не может (ибо другой поток присваивает полю класса значение обратное флагу).

Флаги описаны как voltatile bool, проект собирается с -O0, но это не помогает. При использовании условных точек остановка баг полностью пропадает (вероятно, многопоточность становится не такой многопоточной, если отладчик постоянно останавливает приложение и проверяет условие). Также можно просто забить на флаг и работать дальше вместо выхода из цикла - всё будет идеально работать. Но это не решение, потому что в будущем не сброшенный флаг будет свидетельствовать об ошибке. Пытался создать минимально работающую демонстрацию бага, но не получилось.

Как вообще такое отлаживать? Такое возможно, чтобы, например, это было следствием работы программы на многоядерной системе - значение флага закешировалось у одного ядра и новое дошло до него не сразу? Я пробовал добавлять __sync_synchronize между сбросом флага и вызовом resume, а также между sleep и проверкой флага, но это не помогло.

★★★★★
Ответ на: комментарий от KivApple

Считаю, что тебе пора идти читать про std::thread, std::condition_variable, std::mutex, std::atomic и т.п.

То что ты написал - не C++ а какое-то говно.

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 1)
Ответ на: комментарий от invy

это не «какое-то говно» это pthread. какое-то говно - это искаробочные варианты для нубов, которые заведомо тормозят и сжирают необъяснимое количество ресурсов. пока копошишься с детскими десктопными приложениями, это ещё может проканать. в серьёзных проектах такое недопустимо. к тому же, 99% (если не больше) плюсовых приложений написаны без std::thread (который появился пару лет назад и никому никуда не упирается) и ничо, всё работает.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

(если не больше) плюсовых приложений написаны без std::thread

Они называются legacy или не очень-то и плюсовые :)

slackwarrior ★★★★★
()
Ответ на: комментарий от eao197

Ну да :) Нужно каждый раз писать с нуля всю фигню :) Царь не даст соврать :)

slackwarrior ★★★★★
()
Ответ на: комментарий от slackwarrior

у меня опыта кроссплатформы поболее, чем у многих. и #ifdef'ы рулят, если ты хочешь добиться результата на «просто на отгребись», а действительно качественной работы на каждой платформе, с оптимизацией.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

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

Когда есть бюджет под это дело — не проблема :) Когда бюджета нет — нет такого вопроса. «Качество» — вещь относительная. С тех пор как издержки на вылизывание ручками превышают профит заказчика :)

slackwarrior ★★★★★
()
Ответ на: комментарий от slackwarrior

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от slackwarrior

вот такой подход и губит софт. поэтому и имеем говнософт ан-масс и плюсы уже сравнивают с жабкой. ноги понятно откуда растут.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 2)
Ответ на: комментарий от Iron_Bug

когда у тебя лет десять-двадцать опыта в разработке

40 см, уже все поняли :)

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

На практике это отдельно обсуждается (либо выносится за скобки по умолчанию), и если заказчик «нет» — на нет и суда нет: есть в смете — есть в бюджете, нет в смете — извольте доплатить за :) Лишние ресурсы тратить на это «из любви к превозмоганию искусству» никто не почешется.

slackwarrior ★★★★★
()
Ответ на: комментарий от Iron_Bug

ноги понятно откуда растут.

Из эффективного менеджмента, откуда ж еще-то :) «Обколются своим аджайлом и MBA» :) Дядьки Брукса на них нет.

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Ответ на: комментарий от slackwarrior

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

Что ифдефы? Писать ифдевы в каждом месте юзания тредов? Так это такой лютый и нечитаемый говнокод будет... Или оберточки писать с подменной имплементации для каждом платформы? Но в чем тогда отличие от std/boost::thread?

Dudraug ★★★★★
()
Ответ на: комментарий от Iron_Bug

Ну и да, можно примеры чем std::thread уступает pthread ну что бы это было реально заметно. А не только в синтетическом тесте который в бесконечном цикле создает over 100500 тредов.

Dudraug ★★★★★
()
Ответ на: комментарий от slackwarrior

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Dudraug

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

Это только мне показалось, что нехило так старперским снобизмом пахнуло?

Именно в разработке (т.е. в написании кода, да и в проектировании, отчасти) годы опыта мало что значат. Молодой, но толковый разработчик с 3-мя годами опыта может выдавать код намного лучшего качества, чем середнячок с 20-годами опыта просиживания штанов в большом бодишопе.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 2)
Ответ на: комментарий от eao197

Ух ты, навеное первый годный коммент на лоре от тебя за все время. Молодец, растешь.

anonymous
()
Ответ на: комментарий от Iron_Bug

хорошо знает систему, хорошо знает сетевые протоколы, хорошо знает особенности компиляторов.

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

eao197 ★★★★★
()
Ответ на: комментарий от Iron_Bug

а чем синтетический тест не устраивает?

Тем, что в реальной проге никто в бесконечном треде не гоняет создание, локи/анлоки по 100500 раз. А значит этот «провал» по производительности будет размазан по всему коду и скорее всего вообще заметен не будет. А значит этим можно пожертвовать ради нормальной читаемости и кастомизируемости.

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

Если в приложение используется сеть и/или БД, то вряд ли скорость тредов будет узким местом=)

Dudraug ★★★★★
()
Ответ на: комментарий от eao197

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

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

как-то я на ассемблере(!) делала синхронизацию для одного большого проекта в телекоме.

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

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

это уже от 20 лет постоянного профессионального программирования в предметной области.

Мы, видимо, видели совсем разных гуру. Поэтому нет смысла спорить дальше.

Разве что хочу сказать, что вам, видимо, очень повезло с предметной областью, раз она за 20 лет не поменялась.

eao197 ★★★★★
()
Ответ на: комментарий от slackwarrior

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

Вопрос вот в чём. Допустим, у меня есть код:

while (...) {
    mutex.lock();
    ...
    mutex.unlock();
    ...
}

Что с ним делать? Если бы не было кода после mutex.unlock, то всё понятно, но он есть (в моём случае это отправка события «запись» и переход в ожидание события «чтение», если ещё есть данные для записи).

Можно переписать так:

while (...) {
    {
        MutexLock lock(mutex);
        ...
    }
    ...
}

Но вроде как блоки кода на ровном месте (без if, while и т. п.) не совсем стандарт. Или я не прав и так делать можно и нужно?

Ну или следует вынести код, требующих блокировки, в отдельную функцию (что-нибудь типа writeChunk и readChunk) и вызывать ещё в цикле?

KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

Но вроде как блоки кода на ровном месте (без if, while и т. п.) не совсем стандарт

Кто сказал?

Или я не прав и так делать можно и нужно?

Можно и нужно.

b0r3d0m
()
Ответ на: комментарий от KivApple

Или я не прав и так делать можно и нужно?

Не прав. Можно. Нужно.

Более того, для совсем уж замороченных случаев можно вспомнить о том, что в std::unique_lock есть метод unlock что позволяет, при крайней необходимости, писать вот так:

while(...) {
  std::unique_lock<std::mutex> lock(mutex);
  ...
  lock.unlock();
  ...
}

eao197 ★★★★★
()
Ответ на: комментарий от KivApple

не слушай тех кто выше

надо так:

while (...) {
    ProcessLockedData(...);
    ProcessUnlockedData(...);
}
anonymous
()
Ответ на: комментарий от eao197

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от b0r3d0m

Сделал так:

class MutexLock final {
	private:
		Mutex& m_mutex;
		
		bool m_success;
		
	public:
		MutexLock(Mutex& mutex): m_mutex(mutex) {
			m_success = mutex.lock();
		}
		
		MutexLock(Mutex& mutex, timeout_t timeout): m_mutex(mutex) {
			m_success = mutex.lock(timeout);
		}

		MutexLock(const MutexLock& that) = delete;
		
		~MutexLock() {
			if (m_success) {
				m_mutex.unlock();
			}
		}
		
		bool success() const {
			return m_success;
		}
};

Если что, это не касается mutex внутри sleep и wakeup, потому что там нужны pthread mutex, а не самодельные (CAS + вышеописанный механизм событий), к тому же я уверен, что там не случится внезапный return, ибо функции очень простые.

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

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 2)
Ответ на: комментарий от KivApple

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от anonymous

Каждый день без pthread'a компиляю, прикинь, да?

Не все, как ты, живут в маленьком уютненьком мирке с одной платформой и pthread'ом.

invy ★★★★★
()
Ответ на: как вас ещё на работе держат? от anonymous

И нафига мне создавать копию Mutex при блокировке? У него как бы есть поле bool m_locked. И если у каждого захватившего будет своё поле, то толку от Mutex будет ноль.

А так всё нормально. Описываем Mutex как глобальную переменную или член какого-нибудь класса. А затем при необходимости хватаем, а потом отпускаем.

Я вообще выпилил конструкторы копирования для Mutex, EventSource и EventListener, потому что иначе сломается вся логика их работы.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

Я это знаю. Я не могу похвастаться опытом разработки в 10 лет, но с микроконтроллерами уже какое-то время имею дело. Собственно, для использования внутри прерываний (но не только в них), можно указывать timeout = 0. Например, обработчик прерывания USART может таким образом пихать символы в очередь приёма (а если переполнилась, то что поделать, программист неправильно выбрал размер буфера, либо его такое поведение устраивает).

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Iron_Bug

написаны без std::thread (который появился пару лет назад

c++11, а на дворе 2016 год.

и никому никуда не упирается)

Хз, что вам там и куда не упирается.

и ничо, всё работает.

Ясное дело.

andreyu ★★★★★
()
Ответ на: комментарий от slackwarrior

когда у тебя лет десять-двадцать опыта в разработке

40 см, уже все поняли :)

Боюсь спросить, что у нее 40 см :)

andreyu ★★★★★
()
Ответ на: комментарий от Iron_Bug

каким образом std::thread может замедлить выполнение программы на хоть сколько-нибудь значительную величину? хочется увидеть конкретный пример.

dzidzitop ★★
()
Ответ на: комментарий от Iron_Bug

Точно такая же там структура, просто вместо семафоров при синхронизации потока и обработчика прерывания используется эрзац в виде:

1.Поток разрешает прерывание, засыпает (например, на семафоре), просыпается (его будят) обрабатывает данные.
2.Обработчик прерывания запрещает свое прерывание, сбрасывает его флаг, готовит данные,будит поток (например семафором).

Это та же самая producer-consumer problem, ничего необычного.

shkolnick-kun ★★★★★
()
Ответ на: комментарий от shkolnick-kun

у тебя кто-то должен генерить прерывания(сигналы, что угодно). в софте этого кого-то просто нет. там должен быть мастер.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

Ну так и в обычных программах что-то (чего нет в самой программе) генерирует всякие там SIGKILL, для которых есть обработчики, подобные обработчикам прерываний.

Не вижу особой разницы.

shkolnick-kun ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.