Блокировка, мьютекс, семафор ... какая разница?


439

Я слышал эти слова, связанные с параллельным программированием, но в чем разница между ними?



2
Лучшее объяснение , которое я когда - либо видел: crystal.uta.edu/~ylei/cse6324/data/semaphore.pdf
expoter

Ответы:


534

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

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

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

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

У вас также есть блокировки чтения / записи, которые позволяют неограниченное количество читателей или 1 писатель в любой момент времени.


2
@mertinan Я не могу сказать, что когда-либо слышал об этом, но это то, что википедия говорит: «Защелка (база данных) (относительно недолгая) блокировка структуры данных системы, такой как индекс»
Peter

2
Монитор позволяет ждать определенного состояния (например, при снятии блокировки) «мониторов».
Дмитрий Лазерка

25
Семафор не то же самое, что мьютекс. Они используются по-разному, а также имеют разные свойства (а именно, в отношении владения). См., Например, barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore для получения подробной информации
nanoquack

3
@nanoquack смело редактируйте мой ответ, если считаете, что он вводит в заблуждение или неверен.
Питер

3
Для более четкого различия между мьютексом и семафором, в ссылке на наноквак, ключевой параграф: « Правильное использование семафора для передачи сигналов от одной задачи к другой. Предполагается, что мьютекс должен быть взят и освобожден, всегда в том порядке, каждым задача , которая использует общий ресурс , он защищает Напротив, задачи , которые используют семафоры либо сигнал или ждать, не так.. "
ToolmakerSteve

117

Есть много заблуждений относительно этих слов.

Это из предыдущего поста ( https://stackoverflow.com/a/24582076/3163691 ), который отлично подходит здесь:

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

Нет возможности межпроцессного, очень примитивный объект.

2) Семафор Mutex (также известный как Mutex) = объект ядра, используемый для разрешения выполнения только одного активного потока из множества других, среди различных процессов . Другие не выбранные потоки (@ получение этого объекта) помещаются в спящий режим . Этот объект поддерживает владение потоком, уведомление о прекращении потока, рекурсию (множественные вызовы «получения» из одного потока) и «предотвращение инверсии приоритетов».

Межпроцессная возможность, очень безопасная в использовании, своего рода объект синхронизации «высокого уровня».

3) Подсчет семафора (он же семафор) = объект ядра, используемый для разрешения выполнения группы активных потоков из множества других. Другие не выбранные потоки (@ получение этого объекта) помещаются в спящий режим .

Однако возможность межпроцессного использования не очень безопасна, поскольку в ней отсутствуют следующие атрибуты 'mutex': уведомление о завершении потока, рекурсия ?, 'предотвращение инверсии приоритета'? И т. Д.].

4) А теперь, говоря о спин-замках, сначала несколько определений:

Критическая область = область памяти, совместно используемая двумя или более процессами.

Lock = Переменная, значение которой разрешает или запрещает вход в «критическую область». (Это может быть реализовано как простой «логический флаг»).

Ожидание при занятости = постоянное тестирование переменной до появления какого-либо значения.

В заключение:

Spin-lock (также известный как Spinlock) = блокировка, которая использует ожидание занятым . (Получение блокировки производится с помощью xchg или аналогичных атомарных операций ).

[Нет спящего потока, в основном используется только на уровне ядра. Неэффективный для кода уровня пользователя.

В качестве последнего комментария я не уверен, но могу поспорить с вами, что большие первые синхронизирующие объекты (# 1, # 2 и # 3), указанные выше, используют этого простого зверя (# 4) как часть своей реализации.

Хорошего дня!.

Ссылки:

-Концепции реального времени для встраиваемых систем Цин Ли с Кэролайн Яо (CMP Books).

Современные операционные системы (3-е место) Эндрю Таненбаума (Pearson Education International).

-Программирование приложений для Microsoft Windows (4-е) от Джеффри Рихтера (Microsoft Programming Series).

Кроме того, вы можете взглянуть на: https://stackoverflow.com/a/24586803/3163691


1
На самом деле критическая секция не является объектом ядра, поэтому более легкая и неспособная к синхронизации между процессами.
Владислав Бураков

2
@ Владислав Бураков: Вы правы! Прости мою редакцию. Я исправлю это ради согласованности.
Fante

Для более четкого различия между мьютексом и семафором, как упоминает наноквак в другом месте, см. Barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore - ключевой параграф « Правильное использование семафора для передачи сигналов от одной задачи» к другому. Мьютекс предназначен для взятия и освобождения, всегда в таком порядке, каждой задачей, использующей общий ресурс, который он защищает. Напротив, задачи, использующие семафоры, либо сигнализируют, либо ждут, а не оба. "
ToolmakerSteve

Догадаться, что другие механизмы блокировки основаны на неэффективной спин-блокировке: маловероятно; AFAIK нужны только некоторые атомарные операции плюс очереди ожидания. Даже там , где спинлок это необходимо внутри ядра, современные решения минимизировать его влияние , как описано в Википедии - SpinLock - Альтернативы - " .. использовать гибридный подход , называемый„адаптивный мьютекс“Идея заключается в том , чтобы использовать спинлока при попытке получить доступ к ресурсу блокируется. в текущий момент выполняющихся нить, но спать , если поток не запущен (последнее всегда бывает в однопроцессорных системах.). "
ToolmakerSteve

@ ToolmakerSteve, я осмелюсь предоставить «решение» без «спин-блокировки» для проблемы «столкновений» при попытке «вставить» идентификатор потока в «очередь ожидания». В любом случае, текст Википедии заключает, что при реализации используется спин-блокировка !!!.
Fante

27

Большинство проблем можно решить с помощью (i) просто блокировок, (ii) просто семафоров, ... или (iii) комбинации обоих! Как вы, возможно, обнаружили, они очень похожи: оба препятствуют состязаниям , оба имеют acquire()/ release()операции, оба приводят к блокировке / подозрению на ноль или более потоков ... Действительно, принципиальное различие заключается исключительно в том, как они блокируют и разблокируют .

  • Замок (или мьютекс ) имеет два состояния (0 или 1). Он может быть разблокирован или заблокирован . Они часто используются, чтобы гарантировать, что только один поток входит в критический раздел за один раз.
  • Семафор имеет множество состояний (0, 1, 2, ...). Он может быть заблокирован (состояние 0) или разблокирован (состояния 1, 2, 3, ...). Один или несколько семафоров часто используются вместе, чтобы гарантировать, что только один поток входит в критическую секцию именно тогда, когда число единиц некоторого ресурса достигло / не достигло определенного значения (либо путем обратного отсчета до этого значения, либо путем подсчета до этого значения ).

Для обеих блокировок / семафоров попытка вызова, acquire()когда примитив находится в состоянии 0, приводит к приостановке вызывающего потока. Для блокировок - попытки получить блокировку в состоянии 1 успешны. Для семафоров - попытки получить блокировку в состояниях {1, 2, 3, ...} успешны.

Для блокировок в состоянии 0, если тот же поток, который ранее вызывал acquire(), теперь вызывает release, то релиз успешен. Если другой поток попробовал это - дело за реализацией / библиотекой относительно того, что происходит (обычно попытка игнорируется или выдается ошибка). Для семафоров в состоянии 0 любой поток может вызвать release, и он будет успешным (независимо от того, какой поток использовался ранее, чтобы перевести семафор в состояние 0).

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


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

Важные варианты для рассмотрения :

  • Что должно acquire()/ release()называться? - [Различная массово ]
  • Использует ли ваш замок / семафор «очередь» или «набор» для запоминания ожидающих потоков?
  • Можно ли поделиться вашим замком / семафором с потоками других процессов?
  • Ваш замок "реентерабельный"? - [Обычно да].
  • Ваш замок "блокирующий / неблокирующий"? - [Обычно неблокирующие используются в качестве блокирующих блокировок (так называемые спин-блокировки), вызывающих занятое ожидание].
  • Как вы гарантируете, что операции являются «атомными»?

Это зависит от вашей книги / лектора / языка / библиотеки / окружения.
Вот краткий обзор того, как некоторые языки отвечают на эти детали.


C, C ++ ( pthreads )

  • Мьютекс осуществляется через pthread_mutex_t. По умолчанию они не могут использоваться совместно с другими процессами ( PTHREAD_PROCESS_PRIVATE), однако мьютексы имеют атрибут pshared . Если установлено, то мьютекс является общим для процессов ( PTHREAD_PROCESS_SHARED).
  • Замок это то же самое , как мьютекс.
  • Семафора осуществляется через sem_t. Подобно мьютексам, семафоры могут быть разделены между потоками многих процессов или могут быть приватными для потоков одного отдельного процесса. Это зависит от аргумента pshared, предоставленного sem_init.

питон ( threading.py )

  • Замок ( threading.RLock) в основном такой же , как C / C ++ pthread_mutex_tс. Оба являются реентерабельными . Это означает, что они могут быть разблокированы только тем потоком, который его заблокировал. Это тот случай, когда sem_tсемафоры, threading.Semaphoreсемафоры и theading.Lockблокировки не являются реентерабельными - это тот случай, когда любой поток может выполнить разблокировку / блокировку семафора.
  • Мьютекс такого же , как замок (этот термин часто не используется в Python).
  • Семафора ( threading.Semaphore) в основном такой же , как sem_t. Хотя с sem_tпомощью, очередь идентификаторов потоков используется для запоминания порядка, в котором потоки блокировались при попытке заблокировать его, пока он заблокирован. Когда поток разблокирует семафор, в качестве нового владельца выбирается первый поток в очереди (если он есть). Идентификатор потока удаляется из очереди, и семафор снова блокируется. Однако с threading.Semaphoreпомощью набора используется вместо очереди, поэтому порядок, в котором потоки блокировались, не сохраняется - любой поток в наборе может быть выбран следующим владельцем.

Java ( java.util.concurrent )

  • Замок ( java.util.concurrent.ReentrantLock) в основном так же , как C / C ++ pthread_mutex_t«s и Питон threading.RLockв том , что она также реализует возвратный замок. Совместное использование блокировок между процессами в Java сложнее, поскольку JVM выступает в качестве посредника. Если поток пытается разблокировать блокировку, которой он не владеет, создается IllegalMonitorStateExceptionисключение.
  • Мьютекс такого же , как замок (этот термин часто не используется в Java).
  • Семафора ( java.util.concurrent.Semaphore), в основном такой же , как sem_tи threading.Semaphore. Конструктор для семафоров Java принимает логический параметр fairness, который управляет использованием набора (false) или очереди (true) для хранения ожидающих потоков.

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


22

Взгляните на учебник по многопоточности Джона Копплина.

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

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

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

Другое различие между мьютексом и критической секцией состоит в том, что, если объект критической секции в настоящее время принадлежит другому потоку, он EnterCriticalSection()ожидает неопределенное время владения, тогда как WaitForSingleObject(), который используется с мьютексом, позволяет вам указать тайм-аут

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


15

Я постараюсь покрыть это примерами:

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

private static readonly Object obj = new Object();

lock (obj) //after object is locked no thread can come in and insert item into dictionary on a different thread right before other thread passed the check...
{
    if (!sharedDict.ContainsKey(key))
    {
        sharedDict.Add(item);
    }
}

Семафор: Допустим, у вас есть пул соединений, тогда один поток может зарезервировать один элемент в пуле, ожидая, пока семафор установит соединение. Затем он использует соединение, а когда работа завершена, освобождает соединение, освобождая семафор.

Пример кода, который я люблю, - это пример баунсера, который дал @Patric .

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace TheNightclub
{
    public class Program
    {
        public static Semaphore Bouncer { get; set; }

        public static void Main(string[] args)
        {
            // Create the semaphore with 3 slots, where 3 are available.
            Bouncer = new Semaphore(3, 3);

            // Open the nightclub.
            OpenNightclub();
        }

        public static void OpenNightclub()
        {
            for (int i = 1; i <= 50; i++)
            {
                // Let each guest enter on an own thread.
                Thread thread = new Thread(new ParameterizedThreadStart(Guest));
                thread.Start(i);
            }
        }

        public static void Guest(object args)
        {
            // Wait to enter the nightclub (a semaphore to be released).
            Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
            Bouncer.WaitOne();          

            // Do some dancing.
            Console.WriteLine("Guest {0} is doing some dancing.", args);
            Thread.Sleep(500);

            // Let one guest out (release one semaphore).
            Console.WriteLine("Guest {0} is leaving the nightclub.", args);
            Bouncer.Release(1);
        }
    }
}

Мьютекс В значительной степени Semaphore(1,1)и часто используется во всем мире (в других случаях применение lockболее целесообразно). Можно использовать global Mutexпри удалении узла из глобально доступного списка (последнее, что вы хотите, чтобы другой поток делал что-то, пока вы удаляете узел). Когда вы получите, Mutexесли другой поток попытается получить одно и Mutexто же, он будет переведен в спящий режим, пока тот же поток, который получил его, не Mutexосвободит его.

Хороший пример создания глобального мьютекса - @deepee

class SingleGlobalInstance : IDisposable
{
    public bool hasHandle = false;
    Mutex mutex;

    private void InitMutex()
    {
        string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
        string mutexId = string.Format("Global\\{{{0}}}", appGuid);
        mutex = new Mutex(false, mutexId);

        var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
        var securitySettings = new MutexSecurity();
        securitySettings.AddAccessRule(allowEveryoneRule);
        mutex.SetAccessControl(securitySettings);
    }

    public SingleGlobalInstance(int timeOut)
    {
        InitMutex();
        try
        {
            if(timeOut < 0)
                hasHandle = mutex.WaitOne(Timeout.Infinite, false);
            else
                hasHandle = mutex.WaitOne(timeOut, false);

            if (hasHandle == false)
                throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
        }
        catch (AbandonedMutexException)
        {
            hasHandle = true;
        }
    }


    public void Dispose()
    {
        if (mutex != null)
        {
            if (hasHandle)
                mutex.ReleaseMutex();
            mutex.Dispose();
        }
    }
}

тогда используйте как:

using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
{
    //Only 1 of these runs at a time
    GlobalNodeList.Remove(node)
}

Надеюсь, это сэкономит вам время.


8

В Википедии есть отличный раздел о различиях между семафорами и мьютексами :

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

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

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

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


5

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

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

Может ли кто-нибудь проверить мое объяснение? Я говорю в контексте Linux, в частности Red Hat Enterprise Linux (RHEL) версии 6, которая использует ядро ​​2.6.32.


3
Теперь это может отличаться в разных операционных системах, но в Windows Mutex может использоваться несколькими процессами, по крайней мере, объект .net Mutex ..
Peter

2
stackoverflow.com/questions/9389730/… "Потоки внутри одного и того же процесса или внутри других процессов могут совместно использовать мьютексы". поэтому никакой мьютекс не должен быть специфичным для процесса.
Питер

3

Использование программирования на C для варианта Linux в качестве базового примера.

Замок:

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

• Нет концепции владения потоком, приоритета, последовательности и т. Д.

• Обычно спин-блокировка, когда поток постоянно проверяет наличие блокировок.

• Обычно полагается на атомарные операции, например, «Тестировать и устанавливать», сравнивать и менять, извлекать и добавлять и т. Д.

• Обычно требуется аппаратная поддержка для атомарной работы.

Файловые блокировки:

• Обычно используется для координации доступа к файлу через несколько процессов.

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

• Пример: flock, fcntl и т. Д.

мьютекс:

• Вызовы функции Mutex обычно работают в пространстве ядра и приводят к системным вызовам.

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

• Мьютекс не является рекурсивным (Исключение: PTHREAD_MUTEX_RECURSIVE).

• Обычно используется в ассоциации с переменными условия и передается в качестве аргументов, например, pthread_cond_signal, pthread_cond_wait и т. Д.

• В некоторых системах UNIX мьютекс может использоваться несколькими процессами, хотя это может быть не реализовано во всех системах.

Семафор:

• Это целое число, поддерживаемое ядром, значения которого не могут опускаться ниже нуля.

• Может использоваться для синхронизации процессов.

• Значение семафора может быть установлено на значение больше 1, и в этом случае значение обычно указывает количество доступных ресурсов.

• Семафор, значение которого ограничено 1 и 0, называется двоичным семафором.


0

Supporting ownership, maximum number of processes share lockА также maximum number of allowed processes/threads in critical sectionтри основных фактора , которые определяют имя / тип параллельного объекта с общим названием lock. Поскольку значения этих факторов являются двоичными (имеют два состояния), мы можем суммировать их в 3 * 8 правдоподобной таблице.

  • X (поддерживает право собственности?): Нет (0) / да (1)
  • Y (# процессы обмена):> 1 (∞) / 1
  • Z (# процессов / потоков в CA):> 1 (∞) / 1

  X   Y   Z          Name
 --- --- --- ------------------------
  0   ∞   ∞   Semaphore              
  0   ∞   1   Binary Semaphore       
  0   1   ∞   SemaphoreSlim          
  0   1   1   Binary SemaphoreSlim(?)
  1   ∞   ∞   Recursive-Mutex(?)     
  1   ∞   1   Mutex                  
  1   1   ∞   N/A(?)                 
  1   1   1   Lock/Monitor           

Не стесняйтесь редактировать или расширять эту таблицу, я разместил ее как таблицу ascii, чтобы ее можно было редактировать :)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.