Блокировка, мьютекс, семафор ... какая разница? - PullRequest
355 голосов
/ 25 февраля 2010

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

Ответы [ 8 ]

445 голосов
/ 25 февраля 2010

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

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

A семафор делает то же самое, что и мьютекс, но позволяет вводить x число потоков, это можно использовать, например, для ограничения числа одновременно выполняемых задач с интенсивным использованием процессора, io или ram.

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

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

95 голосов
/ 05 июля 2014

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

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

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

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

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

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

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

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

4) А теперь, говоря о «спинлок», сначала несколько определений:

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

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

Ожидание занято = постоянно проверять переменную, пока не появится какое-либо значение.

Наконец:

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

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

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

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

Ссылки:

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

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

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

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

20 голосов
/ 25 июля 2013

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

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

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

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

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

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

19 голосов
/ 28 января 2017

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

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

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

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

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


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

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

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

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


C, C ++ ( pthreads )

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

python ( threading.py )

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

Java ( java.util.concurrent )

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

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

14 голосов
/ 28 сентября 2016

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

Lock: Одним из примеров, где вы бы использовали 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);
        }
    }
}

Mutex Это в значительной степени Semaphore(1,1) и часто используется во всем мире (более широкое применение, в противном случае, возможно, lock более уместно). Можно использовать глобальный 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)
}

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

7 голосов
/ 28 июля 2013

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

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

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

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

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

5 голосов
/ 11 ноября 2012

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

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

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

2 голосов
/ 14 июня 2017

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

Блокировка:

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

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

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

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

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

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

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

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

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

Мьютекс:

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

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

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

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

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

Семафор:

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

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

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

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

...