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


439

Я чув ці слова, пов’язані з одночасним програмуванням, але в чому різниця між ними?



2
Найкраще пояснення , яке я коли - небудь бачив: crystal.uta.edu/~ylei/cse6324/data/semaphore.pdf
expoter

1
Можливий дублікат Різниці між бінарним

Відповіді:


534

Блокування дозволяє лише одному потоку входити в заблоковану частину, а блокування не ділиться жодними іншими процесами.

Мютекс такий же, як і замок, але він може бути загальносистемним (поділяється кількома процесами).

Семафора робить те ж саме , як взаємна блокування , але дозволяє й кількість потоків , щоб увійти, це може бути використано, наприклад , щоб обмежити число процесорів, Іо ~ d або баран ресурсномістких завдань , що виконуються в той же час.

Більш докладний пост про відмінності мютексу та семафору читайте тут .

Ви також маєте блоки читання / запису, що дозволяє або необмежену кількість читачів, або 1 письменника в будь-який момент часу.


2
@mertinan Я не можу сказати, що я коли-небудь чув про це, але це те, що в wikipedia написано "Засувка (база даних), (відносно недовговічний блокування) в системній структурі даних, як індекс"
Пітер,

2
Монітор дозволяє дочекатися певного стану (наприклад, коли фіксується блокування), "моніторів".
Дмитро Лазерка

25
Семафор не є тим самим, як мютекс. Вони використовуються дуже по-різному, а також мають різні властивості (а саме щодо права власності). Дивіться, наприклад , barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore для деталей
nanoquack

3
@nanoquack не соромтесь відредагувати мою відповідь, якщо ви вважаєте, що вона вводить в оману або неправильно.
Петро

3
Для більш чіткого розрізнення мютексу та семафору, у посиланні наноквадра, ключовий параграф - " Правильне використання семафору - для передачі сигналу від одного завдання до іншого. Мутекс має бути взято та випущено, завжди в тому порядку, кожним із них. завдання, яке використовує спільний ресурс, який він захищає. На відміну від завдань, які використовують семафори або сигнал, або очікування - не те й інше. "
ToolmakerSteve

117

Щодо цих слів багато помилок.

Це з попереднього допису ( https://stackoverflow.com/a/24582076/3163691 ), який тут чудово підходить:

1) Критичний розділ = Об'єкт користувача, який використовується для дозволу виконання лише одного активного потоку з багатьох інших в межах одного процесу . Інші не вибрані нитки (@ придбання цього об'єкта) переходять у режим сну .

[Ніякої можливості міжпроцесорної роботи, дуже примітивний об’єкт].

2) Mutex Semaphore (він же Mutex) = об'єкт ядра, який використовується для дозволу виконання лише одного активного потоку з багатьох інших, в різних процесах . Інші не вибрані нитки (@ придбання цього об'єкта) переходять у режим сну . Цей об’єкт підтримує право власності на потоки, сповіщення про припинення потоку, рекурсію (декілька викликів «придбання» з одного потоку) та «уникнення пріоритетної інверсії».

[Можливість міжпроцесорної роботи, дуже безпечна у використанні, свого роду об'єкт синхронізації високого рівня].

3) Підрахунок Semaphore (він же Semaphore) = об'єкт Kernel, який використовується для дозволу виконання групи активних потоків з багатьох інших. Інші не вибрані нитки (@ придбання цього об'єкта) переходять у режим сну .

[Можливість міжпроцесорної роботи, однак не дуже безпечна для використання, оскільки у неї відсутні такі атрибути "mutex": повідомлення про припинення потоку, рекурсія ?, "уникнення пріоритетної інверсії" тощо тощо.

4) А тепер, говорячи про "спінкі", спочатку кілька визначень:

Критична область = область пам’яті, поділеної двома або більше процесами.

Lock = Змінна, значення якої дозволяє або забороняє вхід до 'критичної області'. (Це може бути реалізовано у вигляді простого "булевого прапора").

Зайняте очікування = Безперервне тестування змінної, поки не з’явиться якесь значення.

Нарешті:

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

[Немає потокових сну, в основному використовується лише на рівні ядра. Неіснуючий код рівня користувача].

Як останнє зауваження, я не впевнений, але я можу поставити під сумнів кілька великих доларів, що вищезгадані перші 3 об’єкти синхронізації (№1, №2 та №3) використовують цей простий звір (№4) як частину їх реалізації.

Гарного дня!.

Список літератури:

-Рецепти реального часу для вбудованих систем Цин Лі з Каролайн Яо (CMP Books).

-Сучасні операційні системи (3-е місце) Ендрю Таненбаума (Pearson Education International).

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

Також ви можете поглянути на: https://stackoverflow.com/a/24586803/3163691


1
Насправді критичний розділ не є об'єктом ядра, тому більш легкий і нездатний синхронізувати між собою процеси.
Владислав Бураков

2
@ Владислав Бураков: Ви праві! Пробачте мою редакцію. Я це виправлю заради узгодженості.
фанте

Для більш чіткого розмежування між м'ютексів і семафором, а nanoquack згадує в іншому місці, см barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore - Ключовий пунктом є " правильним використання семафорів для сигналізації від однієї задачі Мютекс мається на увазі приймати та випускати, завжди в такому порядку, кожне завдання, яке використовує спільний ресурс, який він захищає. На відміну від завдань, які використовують семафори або сигналом, або зачеканням - не обома ".
ToolmakerSteve

Передумову інших механізмів блокування, побудованих на [неефективних] спінлок: малоймовірно; AFAIK потребують лише деяких атомних операцій плюс черги сну. Навіть там , де спінлок це необхідно всередині ядра, сучасні рішення мінімізувати його вплив , як описано в Вікіпедії - SpinLock - Альтернативи - " .. використовувати гібридний підхід , званий" адаптивний м'ютекс "Ідея полягає в тому , щоб використовувати спінлока при спробі отримати доступ до ресурсу блокується. поточна поточна нитка, але спати, якщо нитка наразі не працює. (Останнє завжди є в однопроцесорних системах.) "
ToolmakerSteve

@ToolmakerSteve, я наважуюся надати "рішення" без "спінка" для проблеми "зіткнень", намагаючись "вставити" ідентифікатор потоку до "черги сну". Так чи інакше, у тексті Вікіпедії робиться висновок, що при реалізації використовується спінлок.
фан

27

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

  • Замок (або м'ютекс ) має два стани (0 або 1). Він може бути як розблокований, так і заблокований . Вони часто використовуються для того, щоб одночасно входити лише один потік у критичну секцію.
  • У семафору багато станів (0, 1, 2, ...). Він може бути заблокованим (стан 0) або розблокованим (стани 1, 2, 3, ...). Один або кілька семафорів часто використовуються разом, щоб гарантувати, що лише одна нитка потрапляє у критичний розділ саме тоді, коли кількість одиниць певного ресурсу не досягла певного значення (або шляхом відліку до цього значення, або підрахунку до цього значення ).

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

Для блокувань у стані 0, якщо той самий потік, який раніше викликав acquire(), тепер викликає випуск, то випуск успішний. Якщо інший потік спробував це - він залежить від реалізації / бібліотеки щодо того, що відбувається (зазвичай спроба ігнорується або викидається помилка). Для семафорів у стані 0 будь-яка нитка може викликати вивільнення, і вона буде успішною (незалежно від того, який потік використовувався попередньо, щоб перевести семафор у стан 0).

З попереднього обговорення ми бачимо, що у замках є поняття власника (єдиною ниткою, яка може викликати звільнення, є власник), тоді як семафори не мають власника (будь-яка нитка може викликати звільнення на семафорі).


Що викликає велику плутанину, це те, що на практиці це багато варіантів цього визначення високого рівня.

Важливі варіанти, які слід врахувати :

  • Що повинно acquire()/ release()називатися? - [Варіюється масово ]
  • Чи ваш замок / семафор використовує "чергу" чи "набір", щоб запам'ятати потоки, що чекають?
  • Чи можна ділитися вашим блокуванням / семафором з потоками інших процесів?
  • Ваш замок "регент"? - [Зазвичай так].
  • Ваш замок "блокує / не блокує"? - [Зазвичай неблокуючі використовуються, оскільки блокування блокувань (ака-спін-замків) викликає зайняте очікування].
  • Як ви гарантуєте, що операції є "атомними"?

Це залежить від вашої книги / лектора / мови / бібліотеки / середовища.
Ось короткий тур, зазначаючи, як деякі мови відповідають на ці деталі.


C, C ++ ( pthreads )

  • М'ютекс здійснюється через pthread_mutex_t. За замовчуванням вони не можуть бути надані спільним з будь-якими іншими процесами ( PTHREAD_PROCESS_PRIVATE), однак мутекс має атрибут під назвою pshared . Якщо встановлено, то мютекс поділяється між процесами ( PTHREAD_PROCESS_SHARED).
  • Замок це те ж саме , як м'ютекс.
  • Семафора здійснюється через sem_t. Подібно до мутексів, семафори можуть поділятися між трійками багатьох процесів або залишатись приватними нитками одного єдиного процесу. Це залежить від наданого аргументу PSharedsem_init .

python ( threading.py )

  • Замок ( threading.RLock) в основному такий же , як C / C ++ pthread_mutex_tс. Обоє є регентами . Це означає, що вони можуть бути розблоковані лише тією самою ниткою, яка її заблокувала. Справа в тому, що sem_tсемафори, threading.Semaphoreсемафори та theading.Lockзамки не є ретрансляційними - адже саме така будь-яка нитка може виконати розблокування блокування / вниз семафору.
  • М'ютекс такого ж , як замок (цей термін часто не використовується в Python).
  • Семафора ( threading.Semaphore) в основному такий же , як sem_t. Хоча з sem_t, черга з ідентифікаторами потоку використовується для запам'ятовування порядку, в якому потоки заблокувались при спробі заблокувати її, поки вона заблокована. Коли нитка розблокує семафор, для нового власника вибирається перша нитка в черзі (якщо така є). Ідентифікатор потоку знімається з черги і семафор знову стає заблокованим. Однак, з threading.Semaphore, набір використовується замість черги, тому порядок, в якому потоки заблоковані, не зберігається - будь-який потік у наборі може бути обраний наступним власником.

Java ( java.util.concurrent )

  • Замок ( java.util.concurrent.ReentrantLock) в основному так само , як C / C ++ pthread_mutex_t«s і Пітон threading.RLockв тому , що вона також реалізує поворотний замок. Спільний доступ до блокування між процесами у Java важче через JVM, який виступає посередником. Якщо потік намагається розблокувати замок, якого він не має, IllegalMonitorStateExceptionвикидається анкета.
  • М'ютекс такого ж , як замок (цей термін часто не використовується в Java).
  • Семафора ( java.util.concurrent.Semaphore), в основному такий же , як sem_tі threading.Semaphore. Конструктор для семафорів Java приймає булевий параметр справедливості, який керує тим, чи використовувати набір (false) чи чергу (true) для зберігання потоків очікування.

Теоретично семафори часто обговорюються, але на практиці семафори використовуються не так сильно. Семафор тримає лише одне ціле число, тому часто він є досить негнучким, і багато з них потрібні відразу - спричиняючи труднощі в розумінні коду. Крім того, той факт, що будь-яка нитка може випустити семафор, іноді небажаний. Натомість використовуються більше об'єктно-орієнтовані / примітивні / абстракції синхронізації вищого рівня, такі як "змінні умови" та "монітори".


22

Погляньте на багатопоточний підручник Джона Коппліна.

У розділі Синхронізація між нитками він пояснює відмінності між подією, замком, мютекс, семафором, очікуваним таймером

М'ютекс може належати тільки один потік в той час, дозволяючи теми координувати взаємно виключає доступ до загального ресурсу

Об'єкти критичного розділу забезпечують синхронізацію, аналогічну тій, що надається mutex-об'єктами, за винятком того, що об'єкти критичного розділу можуть використовуватися лише потоками одного процесу

Ще одна відмінність між mutex і критичним розділом полягає в тому, що якщо об'єкт критичного розділу зараз належить іншому потоку, він EnterCriticalSection()чекає нескінченно право власності, тоді як WaitForSingleObject(), який використовується з mutex, дозволяє вказати час очікування

Семафора підтримує підрахунок між нулем і деяким максимальним значенням, обмежуючи кількість потоків, які одночасно доступ до спільно використовуваному ресурсу.


15

Я спробую розкрити це на прикладах:

Блокування. Одним із прикладів використання 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, доцільніше). Можна було б використовувати global Mutexпід час видалення вузла із глобально доступного списку (останнє, що ви хочете, щоб інший потік щось робив під час видалення вузла). Коли ви придбаєте, Mutexякщо інша нитка намагається придбати однакову, Mutexвона буде переведена в режим сну до тих пір, коли SAME, що придбав її, не 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)
}

Сподіваюсь, це заощадить певний час.


8

У Вікіпедії є великий розділ про відмінності між семафорами та мутексами :

Мутекс - це по суті те саме, що і двійковий семафор, і іноді використовує ту саму основну реалізацію. Відмінності між ними:

Mutexes має поняття власника, який є процесом, який замикав мютекс. Тільки процес, який заблокував мютекс, може розблокувати його. На противагу цьому, семафор не має поняття власника. Будь-який процес може розблокувати семафор.

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

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


5

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

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

Чи може хтось підтвердити моє пояснення? Я говорю в контексті Linux, зокрема Red Hat Enterprise Linux (RHEL) версії 6, яка використовує ядро ​​2.6.32.


3
Тепер це може бути різним у різних операційних системах, але у Windows Mutex може використовуватися декількома процесами, принаймні, об'єктом .net Mutex ..
Петро

2
stackoverflow.com/questions/9389730/… "Нитки в межах одного процесу або в межах інших процесів можуть ділитися мутексами." тому жодна мутекс не повинна бути специфічною для процесу.
Пітер

3

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

Блокування:

• Зазвичай дуже простий бінарний конструктор в роботі або заблокований, або розблокований

• Немає концепції власності на потоки, пріоритетності, послідовності тощо.

• Зазвичай спіновий замок, де нитка постійно перевіряє наявність замків.

• Зазвичай покладається на атомні операції, наприклад, тестування та встановлення, порівняння та заміна, отримання та додавання тощо.

• Зазвичай потрібна апаратна підтримка для атомної роботи.

Блокування файлів:

• Зазвичай використовується для координації доступу до файлу через декілька процесів.

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

• Приклад: flock, fcntl тощо.

Mutex:

• Виклики функції Mutex зазвичай працюють у просторі ядра та призводять до системних викликів.

• Він використовує поняття власності. Тільки нитка, в якій зараз міститься мютекс, може розблокувати її.

• Mutex не є рекурсивним (виняток: PTHREAD_MUTEX_RECURSIVE).

• Зазвичай використовується в асоціації зі змінними стану і передається як аргументи, наприклад, pthread_cond_signal, pthread_cond_wait і т.д.

• Деякі системи UNIX дозволяють mutex використовувати декілька процесів, хоча це може бути застосовано не у всіх системах.

Семафор:

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

• Він може використовуватися для синхронізації процесів.

• Значення семафору може бути встановлено на значення, що перевищує 1, і в цьому випадку значення зазвичай вказує кількість наявних ресурсів.

• Семафор, значення якого обмежено 1 і 0, називається бінарним семафором.


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, яку можна редагувати :)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.