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


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

8 309

8 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

занято ожидание= непрерывное тестирование переменной, пока не появится некоторое значение.

и наконец:

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

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

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

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

ссылки:

- В Режиме Реального Времени Концепции для встраиваемых систем от Qing Li с Caroline Yao (CMP Books).

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

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

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

посмотри Многопоточность Учебник Джон Kopplin.

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

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

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

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

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

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

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

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

большинство проблем могут быть решены с помощью (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, то релиз успешно. Если a разные thread попробовал это - это до реализации / библиотеки относительно того, что происходит (обычно попытка игнорируется или выдается ошибка). Для семафоров в состоянии 0,любой поток может вызвать release, и он будет успешным (независимо от того, какой поток ранее использовался, чтобы поместить семафор в состояние 0).

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


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

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

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

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


C, C++ (pthreads)

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

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

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

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

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

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

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

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

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

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

замок:

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

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

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

• обычно полагается на атомарные операции, например Test-and-set, compare-and-swap, fetch-and-add и т. д.

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

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

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

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

• пример: flock, fcntl так далее..

мьютекс:

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

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

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

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

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

семафор:

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

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

• значение семафора может быть установлено на значение больше 1, в этом случае значение обычно указывает на количество имеющиеся ресурсы.

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