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

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

8 ответов


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

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

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

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


есть много неправильных представлений относительно этих слов.

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

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

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

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

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

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

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

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

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

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

Busy waiting= непрерывное тестирование переменной до появления некоторого значения.

и наконец:

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

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

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

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

ссылки:

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

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

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

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


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

В разделе Синхронизация Между Потоками, он объясняет различия между event, lock, mutex, semaphore, waitable timer

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

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

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

A семафор ведет подсчет между нулем и некоторым максимальным значением, ограничение количества потоков, одновременно обращающихся к 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() назвать? -- [варьирует массово]
  • использует ли ваш замок / семафор "очередь" или "набор" для запоминания ожидающих потоков?
  • можно ли совместно использовать блокировку/семафор с потоками других процессов?
  • ваш замок "reentrant"? -- [обычно да].
  • ваш блокировка "блокировка / неблокирование"? -- [обычно неблокирующие используются в качестве блокировочных замков (он же спин-замки) вызывают напряженное ожидание].
  • как вы гарантируете, что операции являются "атомными"?

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


C, C++ (pthreads)

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

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

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

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

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

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


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

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

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


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

замок:

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

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

* обычно замок закрутки где поток непрерывно проверяет для наличия замков.

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

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

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

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

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

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

мьютекс:

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

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

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

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

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

семафор:

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

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

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

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