Подтвердить что ты не робот

Предоставляют ли мьютексы заказы на приобретение?

Недавно у коллеги возникла проблема, которая сводилась к тому, что мы считаем следующей последовательностью событий в приложении С++ с двумя потоками:

  • В потоке A содержится мьютекс.

  • Пока поток A удерживает мьютекс, поток B пытается его заблокировать. Поскольку он удерживается, поток B приостанавливается.

  • Thread A завершает работу, в которой он поддерживает мьютекс, тем самым освобождая мьютекс.

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

  • Кажется, что поток A снова получает мьютекс; нить B все еще ждет, хотя сначала "спросила" о блокировке.

Соответствует ли эта последовательность событий семантике, например, С++ 11 std::mutex и/или pthreads? Я могу честно сказать, что раньше я не думал об этом аспекте мьютексов.

4b9b3361

Ответ 1

Известная проблема. Мьютексы С++ - это тонкий слой поверх флагов OS, и мьютексы, предоставляемые OS, часто не справедливы. Они не интересуются FIFO.

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

Ответ 2

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

Ответ 3

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

В реальном мире, когда программа запущена. нет никакой гарантии, предоставляемой какой-либо библиотекой потоков или операционной системой. Здесь "вскоре после этого" может много значить ОС и аппаратное обеспечение. Если вы скажете, 2 минуты, то поток B определенно получит его. Если вы говорите 200 мс или ниже, нет никаких обещаний получить А или Б.

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

Если вы хотите, чтобы поток B должен получить ресурс, вы можете использовать механизм IPC, чтобы проинструктировать поток B, чтобы получить ресурс.

Ответ 4

Гарантия std:: mutex обеспечивает эксклюзивный доступ к общим ресурсам. Его единственная цель - устранить условие гонки, когда несколько потоков пытаются получить доступ к общим ресурсам.

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

В качестве альтернативы мьютекс можно построить, чтобы предпочесть другой (заблокированный) поток для получения (возможно, выбранный согласно FIFO). Для этого, вероятно, требуется переключатель контекста потока (на том же или другом ядре процессора), увеличивающий задержку/накладные расходы. ПРИМЕЧАНИЕ. Мьютексы FIFO могут вести себя удивительно. Например. Приоритеты потоков должны учитываться в поддержке FIFO - поэтому получение не будет строго FIFO, если все конкурирующие потоки не будут одинаковыми.

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

Защита очереди вызываемых объектов (std:: function) с помощью мьютекса обеспечит выполнение последовательности. Несколько потоков могут приобретать мьютекс, вставлять вызываемый объект и освобождать мьютекс. Вызываемые объекты могут выполняться одним потоком (или пулом потоков, если синхронизация не требуется).

Ответ 5

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

Если вы хотите исправить это, вы всегда можете выдать текущий поток. Вы можете использовать std:: this_thread:: yield() - http://en.cppreference.com/w/cpp/thread/yield - и это может дать возможность потоку B взять мьютекс, Но прежде чем вы это сделаете, позвольте мне сказать вам, что это очень хрупкий способ делать вещи и не дает никаких гарантий. Вы также можете исследовать проблему глубже:

  • Почему возникает проблема, когда поток B не запускается, когда A освобождает ресурс? Ваш код не должен зависеть от такой логики.

  • Рассмотрите возможность использования альтернативных объектов синхронизации нити, таких как барьеры (boost:: барьер или http://linux.die.net/man/3/pthread_barrier_wait), если вам это действительно нужно своего рода логика.

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

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

Ответ 6

Откуда вы знаете это:

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

Как вы знаете, что поток B приостановлен. Откуда вы знаете, что он не просто закончил строку кода, прежде чем пытаться захватить блокировку, но еще не схватил блокировку:

Тема B:

x = 17; // is the thread here?
// or here? ('between' lines of code)
mtx.lock();  // or suspended in here?
// how can you tell?

Вы не можете сказать. По крайней мере, не в теории.

Таким образом, порядок получения блокировки, к абстрактной машине (то есть к языку), не определен.