В какой ситуации вы используете семафор над мьютексом в C++?

Во всех ресурсах, которые я читал о многопоточности, мьютекс чаще используется и обсуждается, чем семафор. Мой вопрос: когда вы используете семафор поверх мьютекса? Я не вижу семафоров в теме Boost. Значит ли это, что семафоры больше не используются в наши дни?

Насколько я понимаю, семафоры позволяют ресурсу делиться несколькими потоками. Это возможно, только если эти потоки только читают ресурс, но не пишут. Это правильно?

9 ответов

Решение

Boost.Thread имеет мьютексы и условные переменные. Чисто с точки зрения функциональности семафоры поэтому избыточны [*], хотя я не знаю, поэтому ли они и опущены.

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

Ресурсы чтения и записи - это ИМО с красной сельдью, оно не имеет никакого отношения к разнице между мьютексом и семафором. Если вы используете счетный семафор, вы можете столкнуться с ситуацией, когда несколько потоков одновременно обращаются к одному и тому же ресурсу, и в этом случае он, вероятно, должен иметь доступ только для чтения. В этой ситуации вы можете использовать shared_mutex из Boost.Thread вместо этого. Но семафоры не "для" защиты ресурсов, как мьютексы, они "для" отправки сигнала из одного потока в другой. Их можно использовать для контроля доступа к ресурсу.

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

[*] Вот примерно как вы реализуете счетный семафор, используя мьютекс и условную переменную. Конечно, для реализации общего семафора вам нужен общий мьютекс /condvar:

struct sem {
    mutex m;
    condvar cv;
    unsigned int count;
};

sem_init(s, value)
    mutex_init(s.m);
    condvar_init(s.cv);
    count = value;

sem_wait(s)
    mutex_lock(s.m);
    while (s.count <= 0) {
        condvar_wait(s.cv, s.m);
    }
    --s.count;
    mutex_unlock(s.m);

sem_post(s)
    mutex_lock(s.m);
    ++s.count;
    condvar_broadcast(s.cv)
    mutex_unlock(s.m);

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

Типичный вариант использования мьютекса (позволяющий доступ к ресурсу только одному потоку в любое время) гораздо более распространен, чем типичный вариант использования семафора. Но семафор на самом деле является более общей концепцией: мьютекс (почти) является частным случаем семафора.

Типичные приложения: Вы не хотите создавать более (например) 5 соединений с базой данных. Независимо от того, сколько рабочих потоков существует, они должны совместно использовать эти 5 соединений. Или, если вы работаете на N-ядерном компьютере, вы можете убедиться, что некоторые задачи, интенсивно использующие ЦП / память, не выполняются в более чем N потоках одновременно (потому что это уменьшит пропускную способность только из-за переключений контекста). и эффекты кеширования кеша). Возможно, вы даже захотите ограничить число параллельных задач, интенсивно использующих процессор / память, до N-1, поэтому остальная часть системы не будет голодать. Или представьте, что определенной задаче требуется много памяти, поэтому одновременное выполнение более N экземпляров этой задачи приведет к подкачке страниц. Вы можете использовать семафор, чтобы убедиться, что одновременно выполняется не более N экземпляров этой конкретной задачи.

РЕДАКТИРОВАТЬ /PS: Из вашего вопроса "Это возможно, только если эти потоки только читают ресурс, но не пишут. Это правильно?" и ваш комментарий, мне кажется, что вы думаете о ресурсе как о переменной или потоке, который может быть прочитан или записан, и который может быть записан только одним потоком за раз. Не. Это вводит в заблуждение в этом контексте.

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

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

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

Поэтому мьютексы обычно используются для одновременной защиты ресурсов (то есть: исключение MUTual), в то время как семафоры используются для передачи сигналов между потоками (например, сигналы флагов семафоров между судами). Мутекс сам по себе не может быть использован для передачи сигналов, но семафоры могут. Таким образом, выбор одного над другим зависит от того, что вы пытаетесь сделать.

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

Для контроля доступа к ограниченному количеству ресурсов, совместно используемых несколькими потоками (внутри- или внутрипроцессными).

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

Итак, мы выделили N из этих ресурсов и поместили их за семафором, инициализированным для N. Когда более чем N потоков пытались использовать ресурс, они просто блокировались, пока один из них не был доступен.

Я чувствую, что нет простого способа ДЕЙСТВИТЕЛЬНО ответить на ваш вопрос, не игнорируя важную информацию о семафорах. Люди написали много книг о семафорах, поэтому любой ответ из одного или двух абзацев - плохая услуга. Популярная книга "Маленькая книга семафоров"... для тех, кто не любит большие книги:).

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

Обновить:
Дэн указал на некоторые ошибки в моих примерах, я оставлю это со ссылками, которые предлагают НАМНОГО лучших объяснений, чем мои:).

Вот ссылки, показывающие ПРАВИЛЬНЫЕ способы использования семафора:
1. Статья IBM
2. Лекция Чикагского университета
3. Статья Netrino, которую я первоначально разместил.
4. "Продажа билетов", бумага + код.

Как взято из этой статьи:

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

Наконец, класс Семафор. Допустим, у вас есть метод, который действительно сильно загружает процессор, а также использует ресурсы, к которым вам нужно контролировать доступ (используя Mutexes:)). Вы также определили, что максимум пять вызовов метода - это почти все, что может обработать ваша машина, не оставляя его без ответа. Лучшим решением здесь является использование класса Semaphore, который позволяет ограничить определенное количество потоков доступа к ресурсу.

Насколько я понимаю, в настоящее время семафоры - это термин, тесно связанный с IPC. Это по-прежнему означает защищенную переменную, которую могут изменять многие процессы, но среди процессов эта функция поддерживается ОС.

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

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

Семафоры изначально задумывались для синхронизации между процессами. Windows использует WaitForMultipleObjects, который похож на семафор. В мире Linux начальная реализация pthread не позволяла использовать мьютекс в процессе. Теперь они делают. Концепция разрушения атомарного приращения (инкрементного приращения в Windows) вместе с легковесным мьютексом является наиболее практичной реализацией в наши дни после того, как потоки стали единицей планирования для процессора. Если инкремент и блокировка были вместе (семафор), время для получения / снятия блокировок будет слишком длинным, и мы не сможем разделить эти две функции модуля, как мы делаем это сегодня для производительности и лучшей конструкции синхронизации.

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

Mutex сильно зависят от реализации. Они были оптимизированы для их двоичной блокировки. Обычный вариант использования мьютекса - это двоичный семафор.

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

Другие вопросы по тегам