04. Mutex. Синхронизация потоков.

Mutex. Синхронизация потоков.

Как было наглядно видно в теме 00 – при работе с потоками они могут обращаться к ресурсам в асинхронном режиме, что приводит к некоторым ошибкам (например некорректный вывод данных в консоль) при работе нескольких потоков одновременно.
Для борьбы с этой проблеммой применяют методы синхронизации потоков (некоторая часть “переехала” из чистого C и RTOS).

Наиболее известные способы:

– Мьютексы (Mutex) –по сути это блокировка ресурса на время выполнения задачи.
– Формирование очередей (упорядоченный список на обработку) что также используется для взаимодействия отдельных потоков.
– Бинарные и счетные семафоры – по сути это производная от очередей, где происходят оповещения от одной задачи другой об освобождении ресурсов, т.е. «общий ресурс» говорит задаче/ам что он свободен, а те если у них есть данные «проверяют ресурс на доступность/свободность ресурса».
Бинарные семафоры могут принимать значения 0 и 1 (по сути как очередь длинны 1),
Cчетные семафоры равносильны очереди длинной N

 

//———————————————————————————————

Рассмотрим «обычный» mutex

Для работы необходимо подключить библиотеку mutex:
#include <mutex>

Напишем следующую программу:

#include <iostream>
#include "thread"
#include "chrono"
#include "string.h"
#include <mutex>

using namespace std;

mutex mtx; //создаем объект класса mutex

void Print5Msg(string msg)
{
   mtx.lock();
    cout<<"Mx Locked"<<endl;
    for (int var = 0; var < 5; ++var)
    {
        cout<<msg<<endl;
        this_thread::sleep_for(chrono::milliseconds(100)); //блокирует/переводит текущий поток в режим ожидания на 100мс
    }
   cout<<"Mx Unlocked"<<endl<<endl;
   mtx.unlock();

}

void Msg1()
{
    cout<<"Task ID="<<this_thread::get_id()<<endl;
    Print5Msg("MSG1");
}

void Msg2()
{
    cout<<"Task ID="<<this_thread::get_id()<<endl; Print5Msg(">>MSG2");
}

int main()
{
    thread task1(&Msg1);
    thread task2(&Msg2);

    task1.join();
    task2.join();
    return 0;
}

В данном примере строки cout<<«Task ID=»<<this_thread::get_id()<<endl;
в обоих потоках не защищены мьютексами и обычно появляются «артефакты при их вызове» (стоит обратить внимание, что стартуют потоки почти одновременно).
Функция Print5Msg(…); имеет защиту в виде мьютексов и хотя потоки стартовали почти одновременно и имеют одинаковую задержку на вывод (100мс) не происходит наложения и смешивания вывода из обоих потоков.

Стоит также отметить, что в конкретно данном случае теряется смысл от многопоточности, т.к. основное действие происходит в однопоточном режиме в функции Print5Msg.

Когда поток доходит до метода mtx.lock(); он проверяет что  ресурс занят, и если да, то ожидает его освобождения.
Если внутри защищенной области использовать код mtx.lock(); (по сути дважды защитить ресурс) – будет ошибка.

Примечание: необходимо не забыть разблокировать mutex!!! Иначе код заблокируется.

 

//———————————————————————————————

Взаимная блокировка:

Как было показано выше – mutex это объект класса, следовательно можно создавать и использовать несколько mutex внутри функций, но тут есть одна опасность.
Рассмотрим на примере:
Пусть есть 2 мьютекса и 2 функции


mutex mtx1; //создаем объект класса mutex
mutex mtx2; //создаем объект класса mutex

void func1()
{
    mtx1.lock();
    //code1
    mxt2.lock();
    //code2
    mxt1.unlock();
    mtx2.unlock();   
}

void func2()
{
    mtx2.lock();
    //code3
    mxt1.lock();
    //code4
    mxt1.unlock();
    mtx2.unlock();   
}

Если функции стартуют одновременно – они обе зависнут на code1 и code3 соответственно, т.к. mxt1.lock(); mxt2.lock();
Для борьбы с подобным можно использовать например одинаковый порядок мьютексов в обеих функциях mtx1.lock(); //code1 mxt2.lock();

 

//———————————————————————————————

Рекурсивный мьютекс:

В рекурсивных функциях может рекурсивно вызываться метод  mutex.lock();, но такой код вызовет ошибку, для рекурсивных функций используется рекурсивный мьютекс.
По сути внутри него есть счетчик, который считает число lock и unlock, поэтому ошибки не возникает.
Важно!!! Освобождать мьютекс надо столько же раз сколько и заблокировали!!!
Синтаксис:
recursive_mutex r_mtx;
методы    r_mtx.lock();  r_mtx.unlock();

 

//———————————————————————————————

lock_guard mutex

lock_guard mutex–по сути немного модифицированный mutex который вызывает mutex.lock(); в конструкторе и  mutex.unlock(); в деструкторе.
Т.е. lock_guard mutex позволяет не задумываться о разблокировке потока.
Минус lock_guard mutex в том, что он блокирует код мьютексом до вызова деструктора, т.е. его можно применять только в однопоточных частях функций (вплоть до выхода из функции).
Т.е. если код –смесь из однопоточной и многопоточных частей – лучше использовать обычный mutex или unique_lock.

синтаксис:

mutex mtx; //создаем объект класса mutex
void Print5Msg(string msg)
{

    lock_guard quard(mtx);
    cout<<"Mx Locked"<<endl;
    for (int var = 0; var < 5; ++var)
    {
        cout<<msg<<endl;
        this_thread::sleep_for(chrono::milliseconds(100)); //блокирует/переводит текущий поток в режим ожидания на 100мс
    }
   cout<<"Mx Unlocked"<<endl<<endl;
} 

 

//———————————————————————————————

unique_lock mutex

Для исправления минуса lock_guard mutex используется unique_lock – он также вызывает mutex.lock(); в конструкторе и mutex.unlock(); в деструкторе ИЛИ В СПЕЦИАЛЬНОМ МЕТОДЕ!
Т.е. если вручную не разблокировать мьютекс – он разблокируется в деструкторе.

mutex mtx; //создаем объект класса mutex
void Print5Msg(string msg)
{

    unique_lock u_lock(mtx);
    cout<<"Mx Locked"<<endl;
    for (int var = 0; var < 5; ++var)
    {
        cout<<msg<<endl;
        this_thread::sleep_for(chrono::milliseconds(100)); //блокирует/переводит текущий поток в режим ожидания на 100мс
    }
   cout<<"Mx Unlocked"<<endl<<endl;
   u_lock.unlock();
   
   //Multithread code
}

Есть некоторые интересные методы:

unique_lock u_lock(mtx,std::defer_lock); //просто создаем объект, не вызывая mtx.lock();
u_lock.lock();                           // вызываем mtx.lock();

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *