Меню

Mutex для синхронизации потоков



Классы Mutex и Semaphore

Mutex

Класс Mutex (mutual exclusion — взаимное исключение или мьютекс) является одним из классов в .NET Framework, позволяющих обеспечить синхронизацию среди множества процессов. Он очень похож на класс Monitor тем, что тоже допускает наличие только одного владельца. Только один поток может получить блокировку и иметь доступ к защищаемым мьютексом синхронизированным областям кода.

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

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

У Mutex имеется несколько конструкторов. Ниже приведены два наиболее употребительных конструктора:

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

Для того чтобы получить мьютекс, в коде программы следует вызвать метод WaitOne() для этого мьютекса. Метод WaitOne() наследуется классом Mutex от класса Thread.WaitHandle. Метод WaitOne() ожидает до тех пор, пока не будет получен мьютекс, для которого он был вызван. Следовательно, этот метод блокирует выполнение вызывающего потока до тех пор, пока не станет доступным указанный мьютекс. Он всегда возвращает логическое значение true.

Когда же в коде больше не требуется владеть мьютексом, он освобождается посредством вызова метода ReleaseMutex().

В приведенном ниже примере программы описанный выше механизм синхронизации демонстрируется на практике. В этой программе создаются два потока в виде классов IncThread и DecThread, которым требуется доступ к общему ресурсу: переменной SharedRes.Count. В потоке IncThread переменная SharedRes.Count инкрементируется, а в потоке DecThread — декрементируется. Во избежание одновременного доступа обоих потоков к общему ресурсу SharedRes.Count этот доступ синхронизируется мьютексом Mtx, также являющимся членом класса SharedRes:

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

Semaphore

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

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

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

Семафор реализуется в классе System.Threading.Semaphore, у которого имеется несколько конструкторов. Ниже приведена простейшая форма конструктора данного класса:

где initialCount — это первоначальное значение для счетчика разрешений семафора, т.е. количество первоначально доступных разрешений; maximumCount — максимальное значение данного счетчика, т.е. максимальное количество разрешений, которые может дать семафор.

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

Если коду больше не требуется владеть семафором, он освобождает его, вызывая метод Release(). Ниже приведены две формы этого метода:

В первой форме метод Release() высвобождает только одно разрешение, а во второй форме — количество разрешений, определяемых параметром releaseCount. В обеих формах данный метод возвращает подсчитанное количество разрешений, существовавших до высвобождения.

Читайте также:  Как синхронизировать контакты с айфона на айфон с outlook

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

Источник

[C++] часть 2: МЬЮТЕКС. Пишем наш первый код для многопоточной среды

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

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

Что такое мьютекс?

Мьютекс (англ. mutex , от mut ual ex clusion — «взаимное исключение») — это базовый механизм синхронизации. Он предназначен для организации взаимоисключающего доступа к общим данным для нескольких потоков с использованием барьеров памяти (для простоты можно считать мьютекс дверью, ведущей к общим данным).

Синтаксис

  • Заголовочный файл | # include
  • Объявление | std::mutex mutex_name;
  • Захват мьютекса | mutex_name .lock() ;
    Поток запрашивает монопольное использование общих данных, защищаемых мьютексом. Дальше два варианта развития событий: происходит захват мьютекса этим потоком (и в этом случае ни один другой поток не сможет получить доступ к этим данным) или поток блокируется (если мьютекс уже захвачен другим потоком).
  • Освобождение мьютекса | mutex_name .unlock() ;
    Когда ресурс больше не нужен, текущий владелец должен вызвать функцию разблокирования unlock, чтобы и другие потоки могли получить доступ к этому ресурсу. Когда мьютекс освобождается, доступ предоставляется одному из ожидающих потоков.

Как создать потокобезопасную очередь

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

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

  • Извлечение и удаление
    Для извлечения и удаления значения из начала очереди необходимо выполнить три операции:
    1. Проверить, не пуста ли очередь.
    2. Если нет, получается ссылка на начало очереди (rawQueue .front() ).
    3. Удаляется начало очереди (rawQueue .pop() ).
    В промежутках между этими этапами к очереди могут получать доступ и другие потоки с целью внесения изменений или чтения. Попробуйте сами .

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

Обратите внимание:

  • Связь между мьютексом и защищаемым ресурсом — только в голове программиста.
    Мы знаем, что мьютекс m защищает rawQueue, но напрямую это не указывается.
  • Захват с необходимой степенью распараллеливания.
    Использование мьютекса уменьшает параллелизм. Предположим, у нас только один мьютекс для защиты вектора и строки без каких-либо зависимостей (например, значение переменной не зависит от вектора и наоборот). Поток А захватывает мьютекс, читает строку и начинает обработку каких-то данных перед добавлением нового значения в вектор и освобождением мьютекса. И тут появляется поток B, который хочет внести пару изменений в строку, но при попытке захватить мьютекс (какая досада!) оказывается блокированным до того момента, пока не будут завершены все операции с вектором. Проблему решаем дополнительным мьютексом для строки ( захваченным перед чтением и освобождённым сразу по завершении ).
    → Всегда прикидывайте, какой объём данных будет защищён одним мьютексом.
  • Проводите захват только для тех операций, которым это необходимо.
    См. предыдущий пункт.
  • Не вызывайте lock() , если мьютекс у вас уже есть.
    Мьютекс уже заблокирован, и попытки повторного захвата приведут вас к состоянию бесконечного ожидания. Если он вам так нужен, можно воспользоваться классом std::recursive_mutex . Рекурсивный мьютекс можно получить одним и тем же потоком много раз, но столько же раз он должен быть и освобождён.
  • Используйте try_lock() или std::timed_mutex , если не хотите блокироваться и ожидать неопределённое время.
    try_lock() — это неблокирующий метод в std::mutex. Он возвращает немедленно, даже если владение не получено, причём со значением true, если мьютекс захвачен, и false — если нет.
    std::timed_mutex предлагает два неблокирующих метода для захвата мьютекса: try_lock_for() и try_lock_until(), причём оба возвращаются по истечении времени со значением true или false в зависимости от успешности захвата.
  • Не забывайте вызывать unlock() или используйте std::lock_guard (или другие шаблонные классы), когда есть возможность.
    См. ниже.

Lock guard и парадигма RAII

У нас две большие проблемы с этим простым мьютексом:

  • Что произойдёт, если мы забудем вызвать unlock() ? Ресурс будет недоступен в течение всего времени существования мьютекса, и уничтожение последнего в неразблокированном состоянии приведёт к неопределённому поведению.
  • Что произойдёт, если до вызова unlock() будет выброшено исключение? unlock() так и не будет исполнен, а у нас будут все перечисленные выше проблемы.

К счастью, проблемы можно решить с помощью класса std::lock_guard. Он всегда гарантированно освобождает мьютекс, используя парадигму RAII (Resource Acquisition Is Initialization, что означает «получение ресурса есть инициализация»). Вот как это происходит: мьютекс инкапсулируется внутри lock_guard , который вызывает lock() в его конструкторе и unlock() в деструкторе при выходе из области видимости. Это безопасно даже при исключениях: раскрутка стека уничтожит lock_guard , вызывая деструктор и таким образом освобождая мьютекс.

Читайте также:  Как синхронизировать мейзу с самсунг

Посмотрим теперь, как можно изменить нашу потокобезопасную очередь threadSafe_queue (на этот раз обращаем внимание на то, где освобождается мьютекс).

Unique lock, дающий свободу

Как только владение мьютексом получено (благодаря std::lock_guard), он может быть освобождён. std::unique_lock действует в схожей манере плюс делает возможным многократный захват и освобождение (всегда в таком порядке) мьютекса , используя те же преимущества безопасности парадигмы RAII.

Когда использовать?

  • Когда вам не всегда нужен захват ресурса.
  • Вместе с std::condition_variable (в следующей статье).
  • При захвате std::shared_mutex в эксклюзивном режиме (см. далее).

Общий мьютекс + общий захват дают больше читателей

std::mutex — это мьютекс, которым одномоментно может владеть только один поток. Однако это ограничение не всегда обязательно. Например, потоки могут одновременно и безопасно читать одни и те же общие данные. Просто читать, не производя с ними никаких изменений. Но в случае с доступом к записи только записывающий поток может иметь доступ к данным.

Начиная с C++17, std::shared_mutex формирует доступ двух типов:

  • Общий доступ : потоки вместе могут владеть мьютексом и иметь доступ к одному и тому же ресурсу. Доступ такого типа можно запросить с помощью std::shared_lock (lock guard для общего мьютекса). При таком доступе любой эксклюзивный доступ блокируется.
  • Эксклюзивный доступ : доступ к ресурсу есть только у одного потока. Запрос этого типа осуществляется с помощью класса unique lock.

Синтаксис

  • Заголовочный файл | # include ;
  • Объявление | std::shared_mutex raw_sharedMutex;
  • Для захвата в общем режиме |
    std::shared_lock std::shared_mutex > sharedLock_name(raw_sharedMutex);
  • Для захвата в эксклюзивном режиме |
    std::unique_lock std::shared_mutex > uniqueLock_name(raw_sharedMutex);

Scoped lock, дающий больше мьютексов (и без клинча)

Впервые появившийся в C++17 и действующий в схожей манере, что и std::lock_guard, он даёт возможность получения нескольких мьютексов . Без std::scoped_lock такая операция очень опасна, так как может привести к взаимной блокировке.

Краткая история взаимоблокировки:

Поток A хочет увести 200$ с банковского счёта Жеки на счёт Бяки в виде одной атомарной операции. Он начинает с того, что захватывает мьютекс, защищающий счёт Жеки, чтобы изъять деньги, а затем пытается захватить счёт Бяки.
В то же время поток B хочет увести 100$ со счёта Бяки на счёт Жеки. Он получает захват счёта Бяки, чтобы изъять деньги и попытаться захватить счёт Жеки. Оба потока блокируются, уснув в ожидании друг друга.

std::scoped_lock одновременно захватывают (а затем освобождают) все мьютексы, передаваемые в качестве аргумента, по принципу « всё или ничего »: если хотя бы один захват выбрасывает исключение, освобождаются все уже захваченные мьютексы.

  • std::scoped_lock scoped_lock_name(raw_mutex1, raw_mutex2, ..);

Заключение

Если вы вдруг запутались в этом ворохе новой информации:

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

До встречи в следующей статье, в которой речь пойдёт о condition_variableи вы узнаете, как синхронизировать потоки!

Источник

Использование mutex для синхронизации двух потоков

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

Надо ли использовать mutex при записи в map из несколькх потоков одновременно?
Такой вопрос, надо ли использовать mutex при записи в map из несколькх потоков одновременно? или.

Простейший случай синхронизации потоков
только изучаю winapi. ситуация наверное банальная, но совсем запутался. например в функции Thread.

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

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

p.s. У Дж.Рихтера в «Создание эффективных Win32-приложений» целая глава по синхронизации есть, и по мьютексам в т.ч.

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

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

Синхронизация потоков без использования mutex
Была написана прога (в целях лабораторной работы) синхронизации потоков,на защиту дали переделать.

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

Проблема синхронизации потоков
Здравствуйте! Нужно показать проблему синхронизации потоков. Помогите пожалуйста. Вот мои.

Пример синхронизации потоков из учебника
Здравствуйте! Я — новичок, поэтому заранее извиняюсь за возможно детский вопрос. Приведу вначале.

Источник

Введение в мьютексы

Обзор мьютексов

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

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

Во-первых, у нас имеется локальная переменная local. Во вторых, используется тяжёлая и медленная функция printf. В тот момент, когда мы присваиваем локальной переменной значение counter, другой поток может в то же самое время взять это значение и поменять.

Для простоты рассмотрим 4 потока – два plus и два minus

Один из возможных вариантов развития собитий при совместном доступе четырёх потоков к переменной

Действие counter plus 1 local plus 2 local minus 1 local minus 2 local
plus 1 помещает в local значение counter
plus 2 помещает в local значение counter
plus 1 инкрементирует local и выводит значение на печать 1
minus 1 помещает в local значение counter 1
plus 1 помещает в переменную counter значение local 1 1
minus 2 помещает в local значение counter 1 1 1
plus 2 инкрементирует local и выводит значение на печать 1 1 1 1
plus 2 помещает в переменную counter значение local 1 1 1 1
minus 2 декрементирует local и выводит значение на печать 1 1 1
minus 2 помещает в переменную counter значение local 1 1
minus 1 декрементирует local и выводит значение на печать 1 1 -1
minus 1 помещает в переменную counter значение local -1 1 1 -1

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

Проблема заключается в том, что у нас имеется несинхронизированный доступ к общему ресурсу. Мы бы хотели сделать так, чтобы на время работы с ресурсом (всё тело функций minus и plus) к ним имел доступ только один поток, а остальные ждали, пока ресурс освободится. Это так называемое mutual exclusion – взаимное исключение, случай, когда необходимо удостовериться в том, что два (и более…) конкурирующих потока не находятся в критической секции кода одновременно.

В библиотеке pthreads один из методов разрешить эту ситуацию – это мьютексы. Мьютекс – это объект, который может находиться в двух состояниях. Он либо заблокирован (занят, залочен, захвачен) каким-то потоком, либо свободен. Поток, который захватил мьютекс, работает с участком кода. Остальные потоки, когда достигают мьютекса, ждут его разблокировки. Разблокировать мьютекс может только тот поток, который его захватил. Обычно освобождение занятого мьютекса происходит после исполнения критичного к совместному доступу участка кода.

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

М ьютекс – это экземпляр типа pthread_mutex_t. Перед использованием необходимо инициализировать мьютекс функцией pthread_mutex_init

где первый аргумент – указатель на мьютекс, а второй – аттрибуты мьютекса. Если указан NULL, то используются атрибуты по умолчанию. В случае удачной инициализации мьютекс переходит в состояние «инициализированный и свободный», а функция возвращает 0. Повторная инициализация инициализированного мьютекса приводит к неопределённому поведению.

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

После использования мьютекса его необходимо уничтожить с помощью функции

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

После создания мьютекса он может быть захвачен с помощью функции

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

Перейдём к реализации. Для начала сделаем глобальный объект мьютекс, доступный всем потокам.

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

И перепишем фунции

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

Остановимся подробнее на функциях для работы с мьютексами

pthread_mutex_init возвращает 0 в случае успешного выполнения. Во время инициализации возможны следующие проблемы

  • EAGAIN – нехватка ресурсов (не памяти) для создания мьютекса
  • ENOMEM — нехватка памяти для инициализации
  • EPERM – нет разрешения на создание

Функция может упасть с ошибкой

  • EBUSY – попытка повторной инициализации не уничтоженного мьютекса.
  • EINVAL – неверные атрибуты функции.

pthread_mutex_destroy может вернуть следующие ошибки

  • EBUSY – попытка уничтожить захваченный мьютекс, или уничтожить мьютекс, на который кто-то ссылается
  • EINVAL – значение мьютекса неверное

pthread_nutex_lock возвращает ошибки

  • EINVAL – неверный приоритет потока

pthread_mutex_lock и pthread_mutex_unlock могут вылететь с ошибками

  • EINVAL – ссылка на объект, который не является мьютексом
  • EAGAIN – нельзя захватить рекурсивный мьютекс, так как превышена максимальная глубина рекурсии
  • EDEADLK – поток, захвативший мьютекс, пытается его взять ещё раз.
  • EPERM – текущий поток не владеет мьютексом (попытка освободить чужой мьютекс)

Источник