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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

Это из предыдущего поста (fooobar.com/questions/26212/...), который отлично подходит здесь:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Рекомендации:

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

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

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

Кроме того, вы можете взглянуть на: fooobar.com/questions/26215/...

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

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

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

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


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

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

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

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


C, С++ (pthreads)

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

python (threading.py)

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

Java (java.util.concurrent)

  • A lock (java.util.concurrent.ReentrantLock) в основном совпадает с 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) для хранения ожидающих потоков.

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

Ответ 5

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

Блокировка:. Один пример, где вы бы использовали 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). При удалении node из глобального списка можно использовать глобальный Mutex (последнее, что вы хотите, чтобы другой поток выполнял что-то, когда вы удаляете node). Когда вы приобретаете Mutex, если другой поток пытается получить тот же Mutex, он будет спать до тех пор, пока SAME-поток, который приобрел 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)
}

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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

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

Ответ 8

мьютекса:

Является ключом к туалету. Один человек может иметь ключ - занимать туалет - в то время. По завершении, человек дает (освобождает) ключ к следующему человеку в очереди.

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

(Мьютекс - действительно семафор со значением 1.)

Семафор:

Является ли количество свободных идентичных туалетных ключей. Например, у нас есть четыре туалета с одинаковыми замками и ключами. Счет семафора - количество ключей - устанавливается в начале 4 (все четыре туалета являются свободными), тогда значение счета уменьшается по мере поступления людей. Если все туалеты заполнены, то есть. свободных ключей нет, счет семафора равен 0. Теперь, когда уравнение один человек выходит из туалета, семафор увеличивается до 1 (один свободный ключ) и передается следующему человеку в очереди.

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

Ответ 9

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

Lock:

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

• Отсутствует концепция владения нитью, приоритета, последовательности и т.д.

• Обычно прямая блокировка, в которой поток непрерывно проверяет наличие блокировок.

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

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

Блокировки файлов:

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

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

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

мьютекса:

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

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

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

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

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

Семафор:

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

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

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

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