Проблемы, связанные с потоками, и их отладка

Это мое продолжение предыдущего сообщения по вопросам управления памятью. Следующие вопросы я знаю.

1)гонки данных (нарушения атомарности и повреждение данных)

2) проблемы с заказом

3)злоупотребление замки ведущих к мертвому замки

4)heisenbugs

любые другие проблемы с многопоточностью ? Как их решить ?

7 ответов


Список Эрика из четырех вопросов в значительной степени на месте. Но отладка этих проблем жесткая.

для deadlock я всегда предпочитал "выровненные замки". По сути, вы даете каждому типу блокировки номер уровня. А затем требуют, чтобы нить aquire блокировки, которые являются монотонными.

чтобы сделать выровненные блокировки, вы можете объявить такую структуру:

typedef struct {
   os_mutex actual_lock;
   int level;
   my_lock *prev_lock_in_thread;
} my_lock_struct;

static __tls my_lock_struct *last_lock_in_thread;

void my_lock_aquire(int level, *my_lock_struct lock) {
    if (last_lock_in_thread != NULL) assert(last_lock_in_thread->level < level)
    os_lock_acquire(lock->actual_lock)
    lock->level = level
    lock->prev_lock_in_thread = last_lock_in_thread
    last_lock_in_thread = lock
}

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

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

компания, в которой я работаю ( http://www.corensic.com ) имеет новый продукт под названием Jinx, который активно ищет случаи, когда условия гонки могут быть подвержены. Это делается с помощью технология виртуализации для управления чередованием потоков на различных процессорах и масштабированием связи между процессорами.

зацените. Вероятно, у вас есть еще несколько дней, чтобы скачать бета-версию бесплатно.

Jinx особенно хорош в поиске ошибок в структурах данных без блокировки. Он также очень хорошо справляется с поиском других условий гонки. Круто то, что нет ложных срабатываний. Если ваше тестирование кода приближается к состоянию гонки, Jinx помогает код идет по плохому пути. Но если плохой путь не существует, вам не будут даны ложные предупреждения.


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

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


четыре наиболее распространенные проблемы с theading являются

1-тупиковая
2-динамической взаимоблокировки
3-Условия Гонки
4-голод


Как решить [проблемы с несколькими потоками]?

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

подготовить и план. Включите хорошее средство регистрации в приложение из начать.


Сделайте ваши потоки максимально простыми.

стараемся не использовать глобальные переменные. Глобальные константы (фактические константы, которые никогда не меняются) в порядке. Когда вам нужно использовать глобальные или общие переменные, вам нужно защитить их с помощью некоторого типа мьютекса / блокировки (семафор, монитор, ...).

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

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

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

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

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

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

вы можете обернуть вызовы в мьютекс блокировка / разблокировка в других функциях, таких как:

инт my_lock_get(lock_type замок, const чарса * файл неподписанные строки, константный тип char * МСГ) {

 thread_id_type me = this_thread();

 logf("%u\t%s (%u)\t%s:%u\t%s\t%s\n", time_now(), thread_name(me), me, "get", msg);

 lock_get(lock);

 logf("%u\t%s (%u)\t%s:%u\t%s\t%s\n", time_now(), thread_name(me), me, "in", msg);

}

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

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

обратите внимание, что функции печати или ведения журнала также могут иметь блокировки. Во многих библиотеках это уже встроено, но не во всех. Эти блокировки не должны использовать печатную версию функций lock_[get|release], иначе у вас будет бесконечная рекурсия.


  1. остерегайтесь глобальных переменных, даже если они const, в частности в С.++ Только под статически инициализированные "à la" C хороши здесь. Как только конструктор времени выполнения вступает в игру, быть весьма осторожный. Порядок инициализации AFAIR переменных со статической связью, которые находятся в различные единицы компиляции вызывается в неопределенном порядке. Возможно Классы C++, которые инициализируют все членов их правильно и пустое тело функции, смогло быть в порядке сейчас, но я однажды было плохо. и с этим тоже опыт.

    это одна из причин, почему на Стороны в POSIX pthread_mutex_t намного легче программировать, чем sem_t: это имеет статический инициализатор PTHREAD_MUTEX_INITIALIZER.

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

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

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

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

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

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


- > добавить инверсию приоритета в этот список.

Как еще один плакат ускользнул, файлы журналов-замечательные вещи. Для тупиков, используя LogLock вместо Lock может помочь определить, когда вы перестаете работать. То есть, как только вы узнаете, что у вас есть тупик, журнал сообщит вам, когда и где были созданы и выпущены блокировки. Это может быть чрезвычайно полезно для отслеживания этих вещей.

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