Блокировка, мьютекс, семафор... какая разница?
Я слышал эти слова, связанные с параллельным программированием, но в чем разница между ними?
11 ответов
Блокировка позволяет только одному потоку войти в заблокированную часть, и блокировка не используется другими процессами.
Мьютекс - это то же самое, что и блокировка, но он может быть системным (общим для нескольких процессов).
Семафор делает то же самое, что и мьютекс, но позволяет вводить x количество потоков, это можно использовать, например, для ограничения количества одновременно выполняемых задач с интенсивным использованием процессора, io или ram.
Более подробный пост о различиях между мьютексом и семафором читайте здесь.
У вас также есть блокировки чтения / записи, которые позволяют неограниченное количество читателей или 1 писатель в любой момент времени.
Есть много заблуждений относительно этих слов.
Это из предыдущего поста ( /questions/4125859/raznitsa-mezhdu-myuteksom-semaforom-i-spin-zamkami/4125871#4125871), который отлично подходит здесь:
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).
Кроме того, вы можете взглянуть на: /questions/13893659/chto-na-samom-dele-delayut-myuteks-i-semafor/13893683#13893683
Большинство проблем можно решить с помощью (i) просто блокировок, (ii) просто семафоров,... или (iii) комбинации обоих! Как вы, возможно, обнаружили, они очень похожи: оба препятствуют гонкам, оба имеют acquire()
/ release()
обе операции приводят к блокировке / подозрению на ноль или более потоков... Действительно, принципиальное различие заключается исключительно в том, как они блокируют и разблокируют.
- Блокировка (или мьютекс) имеет два состояния (0 или 1). Он может быть разблокирован или заблокирован. Они часто используются, чтобы гарантировать, что только один поток входит в критический раздел за один раз.
- Семафор имеет много состояний (0, 1, 2, ...). Он может быть заблокирован (состояние 0) или разблокирован (состояния 1, 2, 3, ...). Один или несколько семафоров часто используются вместе, чтобы гарантировать, что только один поток входит в критическую секцию именно тогда, когда число единиц некоторого ресурса достигло / не достигло определенного значения (либо путем обратного отсчета до этого значения, либо путем подсчета до этого значения).
Для обоих замков / семафоров пытаюсь вызвать acquire()
в то время как примитив находится в состоянии 0, вызывает вызывающий поток, который будет приостановлен. Для блокировок - попытки получить блокировку в состоянии 1 успешны. Для семафоров - попытки получить блокировку в состояниях {1, 2, 3, ...} успешны.
Для блокировок в состоянии 0, если тот же поток, который ранее вызывался acquire()
, теперь вызывает релиз, затем релиз успешен. Если другой поток попробовал это - дело за реализацией / библиотекой относительно того, что происходит (обычно попытка игнорируется или выдается ошибка). Для семафоров в состоянии 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
s. Оба являются реентерабельными. Это означает, что они могут быть разблокированы только тем потоком, который его заблокировал. Это тот случай, когдаsem_t
семафоры,threading.Semaphore
семафоры иtheading.Lock
блокировки не реентерабельны - в этом случае любой поток может выполнить разблокировку / блокировку семафора. - Мьютекс - это то же самое, что и блокировка (этот термин не часто используется в питоне).
- Семафор (
threading.Semaphore
) в основном такой же, какsem_t
, Хотя сsem_t
очередь идентификаторов потоков используется для запоминания порядка, в котором потоки блокировались при попытке заблокировать его, пока он заблокирован. Когда поток разблокирует семафор, в качестве нового владельца выбирается первый поток в очереди (если он есть). Идентификатор потока удаляется из очереди, и семафор снова блокируется. Однако сthreading.Semaphore
вместо очереди используется набор, поэтому порядок блокировки потоков не сохраняется - любой поток в наборе может быть выбран следующим владельцем.
Java ( java.util.concurrent)
- Замок (
java.util.concurrent.ReentrantLock
) в основном такой же, как C/C++pthread_mutex_t
и питонаthreading.RLock
тем, что он также реализует блокировку повторного входа. Совместное использование блокировок между процессами в Java сложнее, поскольку JVM выступает в качестве посредника. Если поток пытается разблокировать блокировку, которой он не владеет,IllegalMonitorStateException
брошен - Мьютекс - это то же самое, что и блокировка (этот термин не часто используется в Java).
- Семафор (
java.util.concurrent.Semaphore
) в основном такой же, какsem_t
а такжеthreading.Semaphore
, Конструктор для семафоров Java принимает логический параметр fairness, который управляет использованием набора (false) или очереди (true) для хранения ожидающих потоков.
В теории семафоры часто обсуждаются, но на практике семафоры используются не так часто. Семафор содержит только одно целое число, поэтому часто он довольно негибкий, и многие нужны сразу, что создает трудности в понимании кода. Кроме того, тот факт, что любой поток может выпустить семафор, иногда нежелателен. Вместо этого используются более объектно-ориентированные / высокоуровневые примитивы / абстракции синхронизации, такие как "переменные условия" и "мониторы".
Взгляните на учебник по многопоточности Джона Копплина.
В разделе " Синхронизация между потоками" он объясняет различия между событием, блокировкой, мьютексом, семафором, ожидаемым таймером.
Мьютекс может принадлежать только одному потоку одновременно, что позволяет потокам координировать взаимоисключающий доступ к общему ресурсу
Объекты критического раздела обеспечивают синхронизацию, аналогичную объектам мьютекса, за исключением того, что объекты критического раздела могут использоваться только потоками одного процесса
Другое отличие между мьютексом и критическим разделом состоит в том, что если объект критического раздела в настоящее время принадлежит другому потоку,
EnterCriticalSection()
бесконечно ждет владения, тогда какWaitForSingleObject()
, который используется с мьютексом, позволяет указать время ожиданияСемафор поддерживает счет от нуля до некоторого максимального значения, ограничивая количество потоков, которые одновременно получают доступ к общему ресурсу.
Я постараюсь покрыть это примерами:
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)
}
Надеюсь, это сэкономит вам время.
В Википедии есть большой раздел о различиях между семафорами и мьютексами:
Мьютекс - это то же самое, что и двоичный семафор, и иногда он использует одну и ту же базовую реализацию. Различия между ними:
У мьютексов есть понятие владельца, то есть процесс, который заблокировал мьютекс. Только процесс, который заблокировал мьютекс, может разблокировать его. В отличие от семафора не имеет понятия владельца. Любой процесс может разблокировать семафор.
В отличие от семафоров, мьютексы обеспечивают безопасность инверсии приоритетов. Поскольку мьютекс знает своего текущего владельца, можно повысить приоритет владельца, когда задача с более высоким приоритетом начинает ожидать мьютекса.
Мьютексы также обеспечивают безопасность удаления, когда процесс, содержащий мьютекс, не может быть случайно удален. Семафоры не предоставляют этого.
Это общее видение. Детали зависят от реальной языковой реализации
- инструмент синхронизации потоков. Когда поток получает блокировку, он становится единственным потоком, который может выполнять блок кода. Все остальные потоки заблокированы. Только поток, которому принадлежит блокировка, может разблокировать его
- блокировка взаимного исключения. Это своего рода замок. На некоторых языках это межпроцессный механизм, на некоторых - синоним. Например, Java использует
lock
в
synchronised
и
java.util.concurrent.locks.Lock
- позволяет нескольким потокам получить доступ к общему ресурсу. Вы можете найти это
mutex
также может быть реализовано
semaphore
. Это отдельный объект, который управляет доступом к общему ресурсу. Вы можете обнаружить, что любой поток может
signal
и разблокировать. Также используется для сигнализации
Насколько я понимаю, мьютекс предназначен только для использования в одном процессе, но во многих его потоках, тогда как семафор может использоваться в нескольких процессах и в соответствующих наборах потоков.
Кроме того, мьютекс является двоичным (он либо заблокирован, либо разблокирован), тогда как семафор имеет понятие подсчета или очередь из нескольких запросов на блокировку и разблокировку.
Может ли кто-нибудь проверить мое объяснение? Я говорю в контексте Linux, в частности Red Hat Enterprise Linux (RHEL) версии 6, которая использует ядро 2.6.32.
Использование C-программирования в варианте Linux в качестве базового примера.
Замок:
• Обычно очень простой двоичный файл конструкции, либо заблокированный, либо разблокированный.
• Нет концепции владения потоком, приоритета, последовательности и т. Д.
• Обычно спин-блокировка, когда поток постоянно проверяет наличие блокировок.
• Обычно полагается на атомарные операции, например, "Тестировать и устанавливать", сравнивать и менять, извлекать и добавлять и т. Д.
• Обычно требуется аппаратная поддержка для атомарной работы.
Файловые блокировки:
• Обычно используется для координации доступа к файлу через несколько процессов.
• Несколько процессов могут удерживать блокировку чтения, однако, когда любой отдельный процесс удерживает блокировку записи, никакому другому процессу не разрешается получать блокировку чтения или записи.
• Пример: flock, fcntl и т. Д.
мьютекс:
• Вызовы функции Mutex обычно работают в пространстве ядра и приводят к системным вызовам.
• Используется концепция собственности. Только поток, который в настоящее время содержит мьютекс, может разблокировать его.
• Мьютекс не является рекурсивным (Исключение: PTHREAD_MUTEX_RECURSIVE).
• Обычно используется в ассоциации с переменными условия и передается в качестве аргументов, например, pthread_cond_signal, pthread_cond_wait и т. Д.
• Некоторые системы UNIX допускают использование мьютекса несколькими процессами, хотя это может быть применено не во всех системах.
Семафор:
• Это целое число, поддерживаемое ядром, значения которого не могут опускаться ниже нуля.
• Может использоваться для синхронизации процессов.
• Значение семафора может быть установлено на значение больше 1, и в этом случае значение обычно указывает количество доступных ресурсов.
• Семафор, значение которого ограничено 1 и 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, чтобы ее можно было редактировать:)
C# предоставляет один оператор (блокировку) и два основных типа примитивов синхронизации, чтобы спасти вас: мьютексы и семафоры.
Чтобы заблокировать ресурс с помощью оператора блокировки и разрешить одновременную работу с ним только одному потоку, используйте синтаксис lock([RESOURCE]){…}:
lock(netWorth) {
...
}
Переменная netWorth заблокирована на время блока кода блокировки (после того, как код покидает блок кода, блокировка снимается) и может быть доступна только одному потоку за раз.